From 4688870984ca44f25a0544ca8cfb500fb9db2e5f Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Thu, 24 Sep 2020 09:01:52 +0200 Subject: [PATCH 01/44] add CredentialProvider interface --- .../kohsuke/github/CredentialProvider.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/org/kohsuke/github/CredentialProvider.java diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java new file mode 100644 index 0000000000..6f9c1a4f15 --- /dev/null +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -0,0 +1,22 @@ +package org.kohsuke.github; + +/** + * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows + * for a provider that dynamically changes the credentials. Each request will request the credentials + * from the provider. + */ +public interface CredentialProvider { + /** + * Returns the credentials to be used with a given request. As an example, a credential + * provider for a bearer token will return something like: + *
{@code
+     *  @Override
+     *  public String getEncodedAuthorization() {
+     *  return "Bearer myBearerToken";
+     *  }
+     * }
+ * + * @return encoded authorization string, can be null + */ + String getEncodedAuthorization(); +} From 3f021f9552536be077939f201b6302a50970df12 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Fri, 25 Sep 2020 10:56:07 +0200 Subject: [PATCH 02/44] lint CredentialProvider --- .../org/kohsuke/github/CredentialProvider.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java index 6f9c1a4f15..4789262442 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -1,20 +1,22 @@ package org.kohsuke.github; /** - * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows - * for a provider that dynamically changes the credentials. Each request will request the credentials - * from the provider. + * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows for a provider that + * dynamically changes the credentials. Each request will request the credentials from the provider. */ public interface CredentialProvider { /** - * Returns the credentials to be used with a given request. As an example, a credential - * provider for a bearer token will return something like: - *
{@code
-     *  @Override
+     * Returns the credentials to be used with a given request. As an example, a credential provider for a bearer token
+     * will return something like:
+     * 
+     * 
+     * {@code
+     *  @Override
      *  public String getEncodedAuthorization() {
      *  return "Bearer myBearerToken";
      *  }
-     * }
+ * } + *
* * @return encoded authorization string, can be null */ From 85d2d974e7d7e7842ce874907135d1859ebcb8eb Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Fri, 25 Sep 2020 12:01:33 +0200 Subject: [PATCH 03/44] add ImmutableCredentialProvider This is basically a class that will hold an authorization string, returning the same value all the time --- .../github/ImmutableCredentialProvider.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java new file mode 100644 index 0000000000..6605d7aa37 --- /dev/null +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -0,0 +1,18 @@ +package org.kohsuke.github; + +/** + * A {@link CredentialProvider} that always returns the same credentials + */ +public class ImmutableCredentialProvider implements CredentialProvider { + + private final String authorization; + + public ImmutableCredentialProvider(String authorization) { + this.authorization = authorization; + } + + @Override + public String getEncodedAuthorization() { + return this.authorization; + } +} From 0e4cd06137f61fc85953368f261ed100cbe654c0 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Fri, 25 Sep 2020 12:02:14 +0200 Subject: [PATCH 04/44] GitHubClient uses CredentialProvider, instead of encodedAuthorization (string) --- .../java/org/kohsuke/github/GitHubClient.java | 19 ++++++++----------- .../github/GitHubHttpUrlConnectionClient.java | 5 +++-- .../kohsuke/github/GitHubConnectionTest.java | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index e59ae5f087..8db01f21b0 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -56,17 +56,13 @@ abstract class GitHubClient { static final int retryTimeoutMillis = 100; /* private */ final String login; - /** - * Value of the authorization header to be sent with the request. - */ - /* private */ final String encodedAuthorization; - // Cache of myself object. private final String apiUrl; protected final RateLimitHandler rateLimitHandler; protected final AbuseLimitHandler abuseLimitHandler; private final GitHubRateLimitChecker rateLimitChecker; + final CredentialProvider credentialProvider; private HttpConnector connector; @@ -112,17 +108,18 @@ abstract class GitHubClient { this.connector = connector; if (oauthAccessToken != null) { - encodedAuthorization = "token " + oauthAccessToken; + this.credentialProvider = new ImmutableCredentialProvider(String.format("token %s", oauthAccessToken)); } else { if (jwtToken != null) { - encodedAuthorization = "Bearer " + jwtToken; + this.credentialProvider = new ImmutableCredentialProvider(String.format("Bearer %s", jwtToken)); } else if (password != null) { String authorization = (login + ':' + password); String charsetName = StandardCharsets.UTF_8.name(); - encodedAuthorization = "Basic " + String encodedAuthorization = "Basic " + Base64.getEncoder().encodeToString(authorization.getBytes(charsetName)); + this.credentialProvider = new ImmutableCredentialProvider(encodedAuthorization); } else {// anonymous access - encodedAuthorization = null; + this.credentialProvider = new ImmutableCredentialProvider(null); } } @@ -130,7 +127,7 @@ abstract class GitHubClient { this.abuseLimitHandler = abuseLimitHandler; this.rateLimitChecker = rateLimitChecker; - if (login == null && encodedAuthorization != null && jwtToken == null) { + if (login == null && credentialProvider.getEncodedAuthorization() != null && jwtToken == null) { GHMyself myself = fetch(GHMyself.class, "/user"); login = myself.getLogin(); if (myselfConsumer != null) { @@ -202,7 +199,7 @@ public void setConnector(HttpConnector connector) { * @return {@code true} if operations that require authentication will fail. */ public boolean isAnonymous() { - return login == null && encodedAuthorization == null; + return login == null && this.credentialProvider.getEncodedAuthorization() == null; } /** diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java index dcc56529a8..89c86cf07f 100644 --- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java +++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java @@ -114,8 +114,9 @@ static HttpURLConnection setupConnection(@Nonnull GitHubClient client, @Nonnull // if the authentication is needed but no credential is given, try it anyway (so that some calls // that do work with anonymous access in the reduced form should still work.) - if (client.encodedAuthorization != null) - connection.setRequestProperty("Authorization", client.encodedAuthorization); + if (client.credentialProvider.getEncodedAuthorization() != null) { + connection.setRequestProperty("Authorization", client.credentialProvider.getEncodedAuthorization()); + } setRequestMethod(request.method(), connection); buildRequest(request, connection); diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java index 882cc3f4b7..4366d10f13 100644 --- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java +++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java @@ -101,7 +101,7 @@ public void testGithubBuilderWithAppInstallationToken() throws Exception { // test authorization header is set as in the RFC6749 GitHub github = builder.build(); // change this to get a request - assertEquals("token bogus", github.getClient().encodedAuthorization); + assertEquals("token bogus", github.getClient().credentialProvider.getEncodedAuthorization()); assertEquals("", github.getClient().login); } From a3888e69023eaf01a88cbfe7ddb63a4f774fa151 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 10:09:54 +0200 Subject: [PATCH 05/44] add CredentialProvider#ANONYMOUS class and field This is basically an implementation of a CredentialProvider that will always authenticate anonymously --- .../kohsuke/github/CredentialProvider.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java index 4789262442..b2e606ae9e 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -1,10 +1,17 @@ package org.kohsuke.github; +import java.io.IOException; + /** * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows for a provider that * dynamically changes the credentials. Each request will request the credentials from the provider. */ public interface CredentialProvider { + /** + * An static instance for an ANONYMOUS credential provider + */ + CredentialProvider ANONYMOUS = new AnonymousCredentialProvider(); + /** * Returns the credentials to be used with a given request. As an example, a credential provider for a bearer token * will return something like: @@ -20,5 +27,15 @@ public interface CredentialProvider { * * @return encoded authorization string, can be null */ - String getEncodedAuthorization(); + String getEncodedAuthorization() throws IOException; + + /** + * A {@link CredentialProvider} that ensures that no credentials are returned + */ + class AnonymousCredentialProvider implements CredentialProvider { + @Override + public String getEncodedAuthorization() throws IOException { + return null; + } + } } From 551be49a1ae6f728424172b1cdc1c23d7782136d Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 10:43:43 +0200 Subject: [PATCH 06/44] utility methods on ImmutableCredentialProvider These methods let us build the most-used cases for static credentials that will never change: - JWT credentials - Token-based credentials - Basic Auth credentials --- .../github/ImmutableCredentialProvider.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java index 6605d7aa37..edc91c8906 100644 --- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -1,5 +1,9 @@ package org.kohsuke.github; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + /** * A {@link CredentialProvider} that always returns the same credentials */ @@ -11,6 +15,53 @@ public ImmutableCredentialProvider(String authorization) { this.authorization = authorization; } + /** + * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken + * + * @param oauthAccessToken + * @return a correctly configured {@link CredentialProvider} that will always return the same provided + * oauthAccessToken + */ + public static CredentialProvider fromOauthToken(String oauthAccessToken) { + return new ImmutableCredentialProvider(String.format("token %s", oauthAccessToken)); + } + + /** + * Builds and returns a {@link CredentialProvider} from a given jwtToken + * + * @param jwtToken + * The JWT token + * @return a correctly configured {@link CredentialProvider} that will always return the same provided jwtToken + * @see jwt.io/introduction + * @see Authenticating + * as a GitHub App + */ + public static CredentialProvider fromJwtToken(String jwtToken) { + return new ImmutableCredentialProvider(String.format("Bearer %s", jwtToken)); + } + + /** + * Builds and returns a {@link CredentialProvider} from the given user/password pair + * + * @param login + * The login for the user, usually the same as the username + * @param password + * The password for the associated user + * @return a correctly configured {@link CredentialProvider} that will always return the credentials for the same + * user and password combo + * @throws UnsupportedEncodingException + * the character encoding is not supported + */ + public static CredentialProvider fromLoginAndPassword(String login, String password) + throws UnsupportedEncodingException { + String authorization = (String.format("%s:%s", login, password)); + String charsetName = StandardCharsets.UTF_8.name(); + String b64encoded = Base64.getEncoder().encodeToString(authorization.getBytes(charsetName)); + String encodedAuthorization = String.format("Basic %s", b64encoded); + return new ImmutableCredentialProvider(encodedAuthorization); + } + @Override public String getEncodedAuthorization() { return this.authorization; From 7b1b1ca9940387968a9ec7b547e531c606e24874 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 12:18:22 +0200 Subject: [PATCH 07/44] ensure that isAnonymous() correctly handles IOException --- src/main/java/org/kohsuke/github/GitHubClient.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 8db01f21b0..353d8004e9 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -199,7 +199,13 @@ public void setConnector(HttpConnector connector) { * @return {@code true} if operations that require authentication will fail. */ public boolean isAnonymous() { - return login == null && this.credentialProvider.getEncodedAuthorization() == null; + try { + return login == null && this.credentialProvider.getEncodedAuthorization() == null; + } catch (IOException e) { + // An exception here means that the provider failed to provide authorization parameters, + // basically meaning the same as "no auth" + return false; + } } /** From e308e5ed57715e2fdce3c38410a1e3ca4c18c6ff Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 12:26:56 +0200 Subject: [PATCH 08/44] use static utility methods instead of building logic in the constructor --- src/main/java/org/kohsuke/github/GitHubClient.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 353d8004e9..4100aad739 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -108,18 +108,14 @@ abstract class GitHubClient { this.connector = connector; if (oauthAccessToken != null) { - this.credentialProvider = new ImmutableCredentialProvider(String.format("token %s", oauthAccessToken)); + this.credentialProvider =ImmutableCredentialProvider.fromOauthToken(oauthAccessToken); } else { if (jwtToken != null) { - this.credentialProvider = new ImmutableCredentialProvider(String.format("Bearer %s", jwtToken)); + this.credentialProvider =ImmutableCredentialProvider.fromJwtToken(jwtToken); } else if (password != null) { - String authorization = (login + ':' + password); - String charsetName = StandardCharsets.UTF_8.name(); - String encodedAuthorization = "Basic " - + Base64.getEncoder().encodeToString(authorization.getBytes(charsetName)); - this.credentialProvider = new ImmutableCredentialProvider(encodedAuthorization); + this.credentialProvider = ImmutableCredentialProvider.fromLoginAndPassword(login,password); } else {// anonymous access - this.credentialProvider = new ImmutableCredentialProvider(null); + this.credentialProvider = ImmutableCredentialProvider.ANONYMOUS; } } From 4f9976c0cb01774f421716f9965a57ad0494e06c Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 12:40:44 +0200 Subject: [PATCH 09/44] add GitHubBuilder#withCredentialProvider With this we also need to check for exceptions when calling "/user", because now we don't know what kind of credentials are coming from the provider, and we could be requesting a "/user" when the type of credentials is not supported --- src/main/java/org/kohsuke/github/GitHub.java | 10 +-- .../org/kohsuke/github/GitHubBuilder.java | 9 ++- .../java/org/kohsuke/github/GitHubClient.java | 69 ++++++++++--------- .../github/GitHubHttpUrlConnectionClient.java | 6 +- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index bb51a2de0e..92c82d92ce 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -87,7 +87,7 @@ public class GitHub { * header. Please note that only operations in which permissions have been previously configured and accepted during * the GitHub App will be executed successfully. * - * + * * @param apiUrl * The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For @@ -101,7 +101,7 @@ public class GitHub { * @param password * User's password. Always used in conjunction with the {@code login} parameter * @param connector - * HttpConnector to use. Pass null to use default connector. + * @param credentialProvider a credential provider, takes preference over all other auth-related parameters if it'ts not null */ GitHub(String apiUrl, String login, @@ -111,7 +111,8 @@ public class GitHub { HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler, - GitHubRateLimitChecker rateLimitChecker) throws IOException { + GitHubRateLimitChecker rateLimitChecker, + CredentialProvider credentialProvider) throws IOException { this.client = new GitHubHttpUrlConnectionClient(apiUrl, login, oauthAccessToken, @@ -121,7 +122,8 @@ public class GitHub { rateLimitHandler, abuseLimitHandler, rateLimitChecker, - (myself) -> setMyself(myself)); + (myself) -> setMyself(myself), + credentialProvider); users = new ConcurrentHashMap<>(); orgs = new ConcurrentHashMap<>(); } diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index e731238f4e..4a1ffc7bf9 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -34,6 +34,7 @@ public class GitHubBuilder implements Cloneable { private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT; private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker(); + private CredentialProvider credentialProvider = null; /** * Instantiates a new Git hub builder. @@ -278,6 +279,11 @@ public GitHubBuilder withOAuthToken(String oauthToken, String user) { return this; } + public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { + this.credentialProvider = credentialProvider; + return this; + } + /** * Configures {@link GitHubBuilder} with Installation Token generated by the GitHub Application * @@ -428,7 +434,8 @@ public GitHub build() throws IOException { connector, rateLimitHandler, abuseLimitHandler, - rateLimitChecker); + rateLimitChecker, + credentialProvider); } @Override diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 4100aad739..18eae96a09 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -1,33 +1,18 @@ package org.kohsuke.github; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.InjectableValues; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.Nullable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InterruptedIOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.nio.charset.StandardCharsets; +import java.net.*; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Consumer; import java.util.logging.Logger; @@ -95,7 +80,8 @@ abstract class GitHubClient { RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler, GitHubRateLimitChecker rateLimitChecker, - Consumer myselfConsumer) throws IOException { + Consumer myselfConsumer, + CredentialProvider credentialProvider) throws IOException { if (apiUrl.endsWith("/")) { apiUrl = apiUrl.substring(0, apiUrl.length() - 1); // normalize @@ -107,15 +93,20 @@ abstract class GitHubClient { this.apiUrl = apiUrl; this.connector = connector; - if (oauthAccessToken != null) { - this.credentialProvider =ImmutableCredentialProvider.fromOauthToken(oauthAccessToken); + // Prefer credential configuration via provider + if (credentialProvider != null) { + this.credentialProvider = credentialProvider; } else { - if (jwtToken != null) { - this.credentialProvider =ImmutableCredentialProvider.fromJwtToken(jwtToken); - } else if (password != null) { - this.credentialProvider = ImmutableCredentialProvider.fromLoginAndPassword(login,password); - } else {// anonymous access - this.credentialProvider = ImmutableCredentialProvider.ANONYMOUS; + if (oauthAccessToken != null) { + this.credentialProvider = ImmutableCredentialProvider.fromOauthToken(oauthAccessToken); + } else { + if (jwtToken != null) { + this.credentialProvider = ImmutableCredentialProvider.fromJwtToken(jwtToken); + } else if (password != null) { + this.credentialProvider = ImmutableCredentialProvider.fromLoginAndPassword(login, password); + } else {// anonymous access + this.credentialProvider = CredentialProvider.ANONYMOUS; + } } } @@ -123,14 +114,24 @@ abstract class GitHubClient { this.abuseLimitHandler = abuseLimitHandler; this.rateLimitChecker = rateLimitChecker; - if (login == null && credentialProvider.getEncodedAuthorization() != null && jwtToken == null) { - GHMyself myself = fetch(GHMyself.class, "/user"); - login = myself.getLogin(); - if (myselfConsumer != null) { - myselfConsumer.accept(myself); + this.login = getCurrentUser(login, jwtToken, myselfConsumer); + } + + @Nullable + private String getCurrentUser(String login, String jwtToken, Consumer myselfConsumer) throws IOException { + if (login == null && this.credentialProvider.getEncodedAuthorization() != null && jwtToken == null) { + try { + GHMyself myself = fetch(GHMyself.class, "/user"); + if (myselfConsumer != null) { + myselfConsumer.accept(myself); + } + return myself.getLogin(); + } catch (IOException e) { + return null; } + } - this.login = login; + return login; } private T fetch(Class type, String urlPath) throws IOException { diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java index 89c86cf07f..b8f5ee840c 100644 --- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java +++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java @@ -41,7 +41,8 @@ class GitHubHttpUrlConnectionClient extends GitHubClient { RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler, GitHubRateLimitChecker rateLimitChecker, - Consumer myselfConsumer) throws IOException { + Consumer myselfConsumer, + CredentialProvider credentialProvider) throws IOException { super(apiUrl, login, oauthAccessToken, @@ -51,7 +52,8 @@ class GitHubHttpUrlConnectionClient extends GitHubClient { rateLimitHandler, abuseLimitHandler, rateLimitChecker, - myselfConsumer); + myselfConsumer, + credentialProvider); } @Nonnull From c038e0af5e1bde6930de386bef5f5b965bb375fb Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 12:51:54 +0200 Subject: [PATCH 10/44] typo it'ts -> it's --- src/main/java/org/kohsuke/github/GitHub.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 92c82d92ce..21d0a3c001 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -101,7 +101,7 @@ public class GitHub { * @param password * User's password. Always used in conjunction with the {@code login} parameter * @param connector - * @param credentialProvider a credential provider, takes preference over all other auth-related parameters if it'ts not null + * @param credentialProvider a credential provider, takes preference over all other auth-related parameters if it's not null */ GitHub(String apiUrl, String login, From 58ae681417a6273b80e060aa9ae85b8040f5f73c Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 12:59:21 +0200 Subject: [PATCH 11/44] reduce visibilitof GitHubBuilder#withCredentialProvider --- src/main/java/org/kohsuke/github/GitHubBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 4a1ffc7bf9..82ad6aed88 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -279,7 +279,7 @@ public GitHubBuilder withOAuthToken(String oauthToken, String user) { return this; } - public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { + GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { this.credentialProvider = credentialProvider; return this; } From aa96089ab4d01410a2bba70532da9d9509b38a33 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:01:55 +0200 Subject: [PATCH 12/44] remove @see to external docs --- .../java/org/kohsuke/github/ImmutableCredentialProvider.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java index edc91c8906..639f0d8ae5 100644 --- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -32,10 +32,6 @@ public static CredentialProvider fromOauthToken(String oauthAccessToken) { * @param jwtToken * The JWT token * @return a correctly configured {@link CredentialProvider} that will always return the same provided jwtToken - * @see jwt.io/introduction - * @see Authenticating - * as a GitHub App */ public static CredentialProvider fromJwtToken(String jwtToken) { return new ImmutableCredentialProvider(String.format("Bearer %s", jwtToken)); From 6d7081910f67a89b0ddc39da667e9138bfd129e0 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:17:46 +0200 Subject: [PATCH 13/44] add OrgInstallationCredentialProvider and JWTTokenProvider The JWTTokenProvider implementation is left to the end user, otherwise we would need to include specific libraries, at least as far as I am aware. The OrgInstallationCredentialProvider will give a token, refreshing when necessary and using the JWTTokenProvider that the user needs to provide to request new tokens --- .../org/kohsuke/github/JWTTokenProvider.java | 15 ++++++ .../OrgInstallationCredentialProvider.java | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/main/java/org/kohsuke/github/JWTTokenProvider.java create mode 100644 src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java diff --git a/src/main/java/org/kohsuke/github/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/JWTTokenProvider.java new file mode 100644 index 0000000000..7b0f5a7df3 --- /dev/null +++ b/src/main/java/org/kohsuke/github/JWTTokenProvider.java @@ -0,0 +1,15 @@ +package org.kohsuke.github; + +/** + * Functional interface to provide a valid JWT Token. Implementations must ensure that subsequent + * calls to {@link #token()} always return a valid and up-to-date token + */ +public interface JWTTokenProvider { + /** + * Returns a valid JWT token for a given application ID, the JWT token can then be used mostly + * on a {@link CredentialProvider} to request an API token for a given installation + * + * @return a valid JWT token + */ + String token(); +} diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java new file mode 100644 index 0000000000..5d17000751 --- /dev/null +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -0,0 +1,47 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.util.Date; + +/** + * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that + * always returns a valid and up-to-date JWT Token. + */ +public class OrgInstallationCredentialProvider implements CredentialProvider { + + + private final String organizationName; + private final JWTTokenProvider jwtTokenProvider; + + private String latestToken; + private Date validUntil; + + /** + * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that + * always returns a valid and up-to-date JWT Token. + * + * @param organizationName The name of the organization where the application is installed + * @param jwtTokenProvider A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens + * for the given application. + */ + public OrgInstallationCredentialProvider(String organizationName, JWTTokenProvider jwtTokenProvider) { + this.organizationName = organizationName; + this.jwtTokenProvider = jwtTokenProvider; + } + + @Override + public String getEncodedAuthorization() throws IOException { + if (latestToken == null || validUntil == null || new Date().compareTo(this.validUntil) > 0) { + refreshToken(); + } + return latestToken; + } + + private void refreshToken() throws IOException { + GitHub gh = new GitHubBuilder().withJwtToken(jwtTokenProvider.token()).build(); + GHAppInstallation installationByOrganization = gh.getApp().getInstallationByOrganization(this.organizationName); + GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create(); + this.validUntil = ghAppInstallationToken.getExpiresAt(); + this.latestToken = ghAppInstallationToken.getToken(); + } +} From a9b7432584881139bc073275753351ff8f1780e9 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:28:13 +0200 Subject: [PATCH 14/44] formatting --- src/main/java/org/kohsuke/github/GitHub.java | 3 ++- src/main/java/org/kohsuke/github/JWTTokenProvider.java | 8 ++++---- .../github/OrgInstallationCredentialProvider.java | 9 ++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 21d0a3c001..9c3efb3834 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -101,7 +101,8 @@ public class GitHub { * @param password * User's password. Always used in conjunction with the {@code login} parameter * @param connector - * @param credentialProvider a credential provider, takes preference over all other auth-related parameters if it's not null + * @param credentialProvider + * a credential provider, takes preference over all other auth-related parameters if it's not null */ GitHub(String apiUrl, String login, diff --git a/src/main/java/org/kohsuke/github/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/JWTTokenProvider.java index 7b0f5a7df3..425af3667e 100644 --- a/src/main/java/org/kohsuke/github/JWTTokenProvider.java +++ b/src/main/java/org/kohsuke/github/JWTTokenProvider.java @@ -1,13 +1,13 @@ package org.kohsuke.github; /** - * Functional interface to provide a valid JWT Token. Implementations must ensure that subsequent - * calls to {@link #token()} always return a valid and up-to-date token + * Functional interface to provide a valid JWT Token. Implementations must ensure that subsequent calls to + * {@link #token()} always return a valid and up-to-date token */ public interface JWTTokenProvider { /** - * Returns a valid JWT token for a given application ID, the JWT token can then be used mostly - * on a {@link CredentialProvider} to request an API token for a given installation + * Returns a valid JWT token for a given application ID, the JWT token can then be used mostly on a + * {@link CredentialProvider} to request an API token for a given installation * * @return a valid JWT token */ diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 5d17000751..7daef8fad1 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -4,12 +4,11 @@ import java.util.Date; /** - * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that - * always returns a valid and up-to-date JWT Token. + * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that always + * returns a valid and up-to-date JWT Token. */ public class OrgInstallationCredentialProvider implements CredentialProvider { - private final String organizationName; private final JWTTokenProvider jwtTokenProvider; @@ -21,8 +20,8 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { * always returns a valid and up-to-date JWT Token. * * @param organizationName The name of the organization where the application is installed - * @param jwtTokenProvider A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens - * for the given application. + * @param jwtTokenProvider A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens for the given + * application. */ public OrgInstallationCredentialProvider(String organizationName, JWTTokenProvider jwtTokenProvider) { this.organizationName = organizationName; From 9480ef485b54e87221ca5aa68b9064c7ecb78640 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:34:14 +0200 Subject: [PATCH 15/44] withCredentialProvider is now public --- src/main/java/org/kohsuke/github/GitHubBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 82ad6aed88..4a1ffc7bf9 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -279,7 +279,7 @@ public GitHubBuilder withOAuthToken(String oauthToken, String user) { return this; } - GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { + public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { this.credentialProvider = credentialProvider; return this; } From 5f9976a1938b232b5801b348e9e1a70320600afd Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:37:59 +0200 Subject: [PATCH 16/44] formatting for OrgInstallationCredentialProvider --- .../kohsuke/github/OrgInstallationCredentialProvider.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 7daef8fad1..e1790ceb26 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -19,9 +19,11 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that * always returns a valid and up-to-date JWT Token. * - * @param organizationName The name of the organization where the application is installed - * @param jwtTokenProvider A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens for the given - * application. + * @param organizationName + * The name of the organization where the application is installed + * @param jwtTokenProvider + * A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens for the given + * application. */ public OrgInstallationCredentialProvider(String organizationName, JWTTokenProvider jwtTokenProvider) { this.organizationName = organizationName; From 83db7f24eb31101caf916b4481f57400fcebe2cb Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:48:26 +0200 Subject: [PATCH 17/44] document CredentialProvider @throws --- src/main/java/org/kohsuke/github/CredentialProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java index b2e606ae9e..602a29d0dd 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -26,6 +26,7 @@ public interface CredentialProvider { * * * @return encoded authorization string, can be null + * @throws IOException on any error that prevents the provider from getting a valid authorization */ String getEncodedAuthorization() throws IOException; From 0d8b4f32e8ea05c9dd132216f5cff468aec11b9d Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 13:48:56 +0200 Subject: [PATCH 18/44] document oauthAccessToken --- .../java/org/kohsuke/github/ImmutableCredentialProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java index 639f0d8ae5..95e88e591d 100644 --- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -18,7 +18,7 @@ public ImmutableCredentialProvider(String authorization) { /** * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken * - * @param oauthAccessToken + * @param oauthAccessToken The token * @return a correctly configured {@link CredentialProvider} that will always return the same provided * oauthAccessToken */ From 29ac2bd4f5fde7d01d6062dd6dda65a46600fa70 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 14:24:01 +0200 Subject: [PATCH 19/44] return a correctly formatted token --- .../org/kohsuke/github/OrgInstallationCredentialProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index e1790ceb26..48d9e47790 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -35,7 +35,7 @@ public String getEncodedAuthorization() throws IOException { if (latestToken == null || validUntil == null || new Date().compareTo(this.validUntil) > 0) { refreshToken(); } - return latestToken; + return String.format("token %s", latestToken); } private void refreshToken() throws IOException { From 0c65f74662a37943f21a8d9a7c04c0c08e7f4b6b Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 14:45:00 +0200 Subject: [PATCH 20/44] formatting --- src/main/java/org/kohsuke/github/CredentialProvider.java | 3 ++- .../java/org/kohsuke/github/ImmutableCredentialProvider.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java index 602a29d0dd..6f8ae6833e 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -26,7 +26,8 @@ public interface CredentialProvider { * * * @return encoded authorization string, can be null - * @throws IOException on any error that prevents the provider from getting a valid authorization + * @throws IOException + * on any error that prevents the provider from getting a valid authorization */ String getEncodedAuthorization() throws IOException; diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java index 95e88e591d..69f54331cf 100644 --- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -18,7 +18,8 @@ public ImmutableCredentialProvider(String authorization) { /** * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken * - * @param oauthAccessToken The token + * @param oauthAccessToken + * The token * @return a correctly configured {@link CredentialProvider} that will always return the same provided * oauthAccessToken */ From bb03fd19681c53bdcd2284e7c32c286e7330548f Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 28 Sep 2020 16:11:16 +0200 Subject: [PATCH 21/44] use Date#after instead of compareTo --- .../org/kohsuke/github/OrgInstallationCredentialProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 48d9e47790..a1c3ad7cf1 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -32,7 +32,7 @@ public OrgInstallationCredentialProvider(String organizationName, JWTTokenProvid @Override public String getEncodedAuthorization() throws IOException { - if (latestToken == null || validUntil == null || new Date().compareTo(this.validUntil) > 0) { + if (latestToken == null || validUntil == null || new Date().after(this.validUntil)) { refreshToken(); } return String.format("token %s", latestToken); From a0fc478a2831b3cf1059745548f9f8e0a4a9ba93 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Tue, 29 Sep 2020 16:12:06 +0200 Subject: [PATCH 22/44] remove final modifier from credentialProvider (required for tests) --- src/main/java/org/kohsuke/github/GitHubClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 18eae96a09..963fc3f886 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -47,7 +47,7 @@ abstract class GitHubClient { protected final RateLimitHandler rateLimitHandler; protected final AbuseLimitHandler abuseLimitHandler; private final GitHubRateLimitChecker rateLimitChecker; - final CredentialProvider credentialProvider; + CredentialProvider credentialProvider; private HttpConnector connector; From 4f3099887352823d837a328e63575a92a63fea2a Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Tue, 29 Sep 2020 16:13:23 +0200 Subject: [PATCH 23/44] OrgInstallationCredentialProvider now receives a pre-configured client This is required to pass integration tests. In terms of functionality, the user should be able to provide a client with the given token provider. It additionally increases control (e.g: usage of proxies) Add tests --- .../OrgInstallationCredentialProvider.java | 16 +++---- ...OrgInstallationCredentialProviderTest.java | 32 +++++++++++++ .../mappings/app-2.json | 35 ++++++++++++++ .../mappings/user-1.json | 39 +++++++++++++++ .../__files/app-2.json | 39 +++++++++++++++ .../orgs_hub4j-test-org_installation-3.json | 43 +++++++++++++++++ .../mappings/app-2.json | 41 ++++++++++++++++ ...nstallations_11575015_access_tokens-4.json | 48 +++++++++++++++++++ .../orgs_hub4j-test-org_installation-3.json | 41 ++++++++++++++++ .../mappings/user-1.json | 39 +++++++++++++++ 10 files changed, 365 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json create mode 100644 src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index a1c3ad7cf1..4118494bf6 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -9,8 +9,9 @@ */ public class OrgInstallationCredentialProvider implements CredentialProvider { + private final GitHub gitHub; + private final String organizationName; - private final JWTTokenProvider jwtTokenProvider; private String latestToken; private Date validUntil; @@ -21,13 +22,12 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { * * @param organizationName * The name of the organization where the application is installed - * @param jwtTokenProvider - * A {@link JWTTokenProvider} that always returns valid and up-to-date JWT Tokens for the given - * application. + * @param gitHub + * A GitHub client that must be configured with a valid JWT token */ - public OrgInstallationCredentialProvider(String organizationName, JWTTokenProvider jwtTokenProvider) { + public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub) { this.organizationName = organizationName; - this.jwtTokenProvider = jwtTokenProvider; + this.gitHub = gitHub; } @Override @@ -39,8 +39,8 @@ public String getEncodedAuthorization() throws IOException { } private void refreshToken() throws IOException { - GitHub gh = new GitHubBuilder().withJwtToken(jwtTokenProvider.token()).build(); - GHAppInstallation installationByOrganization = gh.getApp().getInstallationByOrganization(this.organizationName); + GHAppInstallation installationByOrganization = gitHub.getApp() + .getInstallationByOrganization(this.organizationName); GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create(); this.validUntil = ghAppInstallationToken.getExpiresAt(); this.latestToken = ghAppInstallationToken.getToken(); diff --git a/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java b/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java new file mode 100644 index 0000000000..dd65c43b1b --- /dev/null +++ b/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java @@ -0,0 +1,32 @@ +package org.kohsuke.github; + +import net.sf.ezmorph.test.ArrayAssertions; +import org.junit.Test; + +import java.io.IOException; + +public class OrgInstallationCredentialProviderTest extends AbstractGitHubWireMockTest { + + @Test(expected = HttpException.class) + public void invalidJWTTokenRaisesException() throws IOException { + + gitHub.getClient().credentialProvider = ImmutableCredentialProvider.fromJwtToken("myToken"); + + OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("testOrganization", gitHub); + + provider.getEncodedAuthorization(); + } + + @Test + public void validJWTTokenAllowsOauthTokenRequest() throws IOException { + gitHub.getClient().credentialProvider = ImmutableCredentialProvider.fromJwtToken("valid-token"); + + OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("hub4j-test-org", gitHub); + + String encodedAuthorization = provider.getEncodedAuthorization(); + + ArrayAssertions.assertNotNull(encodedAuthorization); + ArrayAssertions.assertEquals("token v1.9a12d913f980a45a16ac9c3a9d34d9b7sa314cb6", encodedAuthorization); + } + +} diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json new file mode 100644 index 0000000000..5849fd75c7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json @@ -0,0 +1,35 @@ +{ + "id": "960b4085-803f-43aa-a291-ccb6fd003adb", + "name": "app", + "request": { + "url": "/app", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github.machine-man-preview+json" + } + } + }, + "response": { + "status": 401, + "body": "{\"message\":\"A JSON web token could not be decoded\",\"documentation_url\":\"https://docs.github.com/rest\"}", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:35 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "401 Unauthorized", + "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "D236:47C4:1909E17E:1DD010FD:5F732A16" + } + }, + "uuid": "960b4085-803f-43aa-a291-ccb6fd003adb", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json new file mode 100644 index 0000000000..feea2b1f2d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json @@ -0,0 +1,39 @@ +{ + "id": "31df960e-9966-4b89-8a99-0d6688accca9", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 401, + "body": "{\"message\":\"Bad credentials\",\"documentation_url\":\"https://docs.github.com/rest\"}", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:34 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "401 Unauthorized", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "60", + "X-RateLimit-Remaining": "55", + "X-RateLimit-Reset": "1601386475", + "X-RateLimit-Used": "5", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "D236:47C4:1909E038:1DD010AD:5F732A16" + } + }, + "uuid": "31df960e-9966-4b89-8a99-0d6688accca9", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json new file mode 100644 index 0000000000..4442d5c635 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json @@ -0,0 +1,39 @@ +{ + "id": 79253, + "slug": "hub4j-test-application", + "node_id": "MDM6QXBwNzkyNTM=", + "owner": { + "login": "hub4j-test-org", + "id": 70590530, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcwNTkwNTMw", + "avatar_url": "https://avatars1.githubusercontent.com/u/70590530?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "hub4j-test-application", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/hub4j-test-application", + "created_at": "2020-09-01T14:56:16Z", + "updated_at": "2020-09-01T14:56:16Z", + "permissions": { + "metadata": "read", + "pull_requests": "write" + }, + "events": [ + "pull_request" + ], + "installations_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json new file mode 100644 index 0000000000..80a6c2db36 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json @@ -0,0 +1,43 @@ +{ + "id": 11575015, + "account": { + "login": "hub4j-test-org", + "id": 70590530, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcwNTkwNTMw", + "avatar_url": "https://avatars1.githubusercontent.com/u/70590530?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "repository_selection": "all", + "access_tokens_url": "https://api.github.com/app/installations/11575015/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/organizations/hub4j-test-org/settings/installations/11575015", + "app_id": 79253, + "app_slug": "hub4j-test-application", + "target_id": 70590530, + "target_type": "Organization", + "permissions": { + "metadata": "read", + "pull_requests": "write" + }, + "events": [ + "pull_request" + ], + "created_at": "2020-09-01T14:56:49.000Z", + "updated_at": "2020-09-01T14:56:49.000Z", + "single_file_name": null, + "suspended_by": null, + "suspended_at": null +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json new file mode 100644 index 0000000000..86b4f0076e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json @@ -0,0 +1,41 @@ +{ + "id": "7b483ea8-ace3-4af3-ae23-b081d717fa53", + "name": "app", + "request": { + "url": "/app", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github.machine-man-preview+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "app-2.json", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:36 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": [ + "Accept", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "W/\"a4f1cab410e5b80ee9775d1ecb4d3296f067ddcdfa22ba2122dd382c992b55fe\"", + "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "D11A:F68D:17924B62:1C1232BE:5F732A18" + } + }, + "uuid": "7b483ea8-ace3-4af3-ae23-b081d717fa53", + "persistent": true, + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json new file mode 100644 index 0000000000..68600667e9 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json @@ -0,0 +1,48 @@ +{ + "id": "7e25da60-68c9-41c5-b603-359192783583", + "name": "app_installations_11575015_access_tokens", + "request": { + "url": "/app/installations/11575015/access_tokens", + "method": "POST", + "headers": { + "Accept": { + "equalTo": "application/vnd.github.machine-man-preview+json" + } + }, + "bodyPatterns": [ + { + "equalToJson": "{}", + "ignoreArrayOrder": true, + "ignoreExtraElements": false + } + ] + }, + "response": { + "status": 201, + "body": "{\"token\":\"v1.9a12d913f980a45a16ac9c3a9d34d9b7sa314cb6\",\"expires_at\":\"2020-09-29T13:35:37Z\",\"permissions\":{\"metadata\":\"read\",\"pull_requests\":\"write\"},\"repository_selection\":\"all\"}", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:37 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "201 Created", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": [ + "Accept", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "\"168d81847da026cae71dddc5658dc87c05a2b6945d4e635787c451df823fc72a\"", + "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "D11A:F68D:17924C69:1C12341C:5F732A18" + } + }, + "uuid": "7e25da60-68c9-41c5-b603-359192783583", + "persistent": true, + "insertionIndex": 4 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json new file mode 100644 index 0000000000..54e1ea9d4d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json @@ -0,0 +1,41 @@ +{ + "id": "9ffe1e34-1d0e-495a-abdc-86fdf1d15334", + "name": "orgs_hub4j-test-org_installation", + "request": { + "url": "/orgs/hub4j-test-org/installation", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "application/vnd.github.machine-man-preview+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "orgs_hub4j-test-org_installation-3.json", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:36 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": [ + "Accept", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "W/\"5fa17d9ba74cf1c58441056ab43311b39f39e78976e8524ad3962278c5224955\"", + "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "D11A:F68D:17924BFB:1C12335A:5F732A18" + } + }, + "uuid": "9ffe1e34-1d0e-495a-abdc-86fdf1d15334", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json new file mode 100644 index 0000000000..ca0c3dee38 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json @@ -0,0 +1,39 @@ +{ + "id": "85ae1237-62c3-4f75-888b-8d751677aa07", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 401, + "body": "{\"message\":\"Bad credentials\",\"documentation_url\":\"https://docs.github.com/rest\"}", + "headers": { + "Date": "Tue, 29 Sep 2020 12:35:36 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "401 Unauthorized", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "60", + "X-RateLimit-Remaining": "53", + "X-RateLimit-Reset": "1601386475", + "X-RateLimit-Used": "7", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "D11A:F68D:17924B00:1C12327C:5F732A17" + } + }, + "uuid": "85ae1237-62c3-4f75-888b-8d751677aa07", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file From 97e918da0312d1fe90f3dcd5d0c048accd2e226f Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Wed, 30 Sep 2020 16:39:41 +0200 Subject: [PATCH 24/44] remove unused JWTTokenProvider (we are now using a github client) --- .../java/org/kohsuke/github/JWTTokenProvider.java | 15 --------------- .../github/OrgInstallationCredentialProvider.java | 7 +++---- 2 files changed, 3 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/org/kohsuke/github/JWTTokenProvider.java diff --git a/src/main/java/org/kohsuke/github/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/JWTTokenProvider.java deleted file mode 100644 index 425af3667e..0000000000 --- a/src/main/java/org/kohsuke/github/JWTTokenProvider.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.kohsuke.github; - -/** - * Functional interface to provide a valid JWT Token. Implementations must ensure that subsequent calls to - * {@link #token()} always return a valid and up-to-date token - */ -public interface JWTTokenProvider { - /** - * Returns a valid JWT token for a given application ID, the JWT token can then be used mostly on a - * {@link CredentialProvider} to request an API token for a given installation - * - * @return a valid JWT token - */ - String token(); -} diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 4118494bf6..7fdd8bf616 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -4,8 +4,7 @@ import java.util.Date; /** - * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that always - * returns a valid and up-to-date JWT Token. + * Provides a CredentialProvider that performs automatic token refresh. */ public class OrgInstallationCredentialProvider implements CredentialProvider { @@ -17,8 +16,8 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { private Date validUntil; /** - * Provides a CredentialProvider that performs automatic token refresh based on a {@link JWTTokenProvider} that - * always returns a valid and up-to-date JWT Token. + * Provides a CredentialProvider that performs automatic token refresh, based on an previously + * authenticated github client. * * @param organizationName * The name of the organization where the application is installed From ff790eeefbb038ef97f0dc667e1fa24ea386f4a9 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Wed, 30 Sep 2020 16:48:54 +0200 Subject: [PATCH 25/44] formatting of OrgInstallationCredentialProvider.java --- .../org/kohsuke/github/OrgInstallationCredentialProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 7fdd8bf616..34fc05f0e2 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -16,8 +16,8 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { private Date validUntil; /** - * Provides a CredentialProvider that performs automatic token refresh, based on an previously - * authenticated github client. + * Provides a CredentialProvider that performs automatic token refresh, based on an previously authenticated github + * client. * * @param organizationName * The name of the organization where the application is installed From 59e18d155e02bbf324dc56bf7fc3a8abf0c47e67 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 5 Oct 2020 13:39:01 +0200 Subject: [PATCH 26/44] add dependencies for jwt token generation These dependencies are marked as "provided" because they are only used in the extras package --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index bbd46889bc..09a2ce6439 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ 0.25 false + 0.11.1 @@ -540,6 +541,26 @@ 2.8.6 test + + + io.jsonwebtoken + jjwt-api + ${jjwt.suite.version} + provided + + + io.jsonwebtoken + jjwt-impl + ${jjwt.suite.version} + provided + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.suite.version} + provided + + From 8a474a3b00ad0467b502aa801d9f5c807d6ef162 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 5 Oct 2020 13:39:30 +0200 Subject: [PATCH 27/44] add: example for Org Installation token on extras package --- .../github/extras/auth/JWTTokenProvider.java | 83 +++++++++++++++++++ .../OrgInstallationCredentialProvider.java | 70 ++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java create mode 100644 src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java new file mode 100644 index 0000000000..8e2234a2e6 --- /dev/null +++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java @@ -0,0 +1,83 @@ +package org.kohsuke.github.extras.auth; + +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.kohsuke.github.CredentialProvider; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.Duration; +import java.util.Date; + +/** + * A credential provider that gives valid JWT tokens. These tokens are then used to create a time-based token to + * authenticate as an application. This token provider does not provide any kind of caching, and will always request a + * new token to the API. + */ +public class JWTTokenProvider implements CredentialProvider { + + private static final long MINUTES_10 = Duration.ofMinutes(10).toMillis(); + + private final PrivateKey privateKey; + + /** + * The identifier for the application + */ + private final String applicationId; + + public JWTTokenProvider(String applicationId, Path keyPath) + throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { + this.privateKey = loadPrivateKey(keyPath); + this.applicationId = applicationId; + } + + /**add dependencies for a jwt suite + * You can generate a key to load with this method with: + * + *
+     * openssl pkcs8 -topk8 -inform PEM -outform DER -in ~/github-api-app.private-key.pem -out ~/github-api-app.private-key.der -nocrypt
+     * 
+ */ + private PrivateKey loadPrivateKey(Path keyPath) + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + + byte[] keyBytes = Files.readAllBytes(keyPath); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(spec); + } + + public String getJWT() { + long nowMillis = System.currentTimeMillis(); + Date now = new Date(nowMillis); + + // Let's set the JWT Claims + JwtBuilder builder = Jwts.builder() + .setIssuedAt(now) + .setIssuer(this.applicationId) + .signWith(privateKey, SignatureAlgorithm.RS256); + + // if it has been specified, let's add the expiration + if (MINUTES_10 > 0) { + long expMillis = nowMillis + MINUTES_10; + Date exp = new Date(expMillis); + builder.setExpiration(exp); + } + + // Builds the JWT and serializes it to a compact, URL-safe string + return builder.compact(); + } + + @Override + public String getEncodedAuthorization() throws IOException { + return getJWT(); + } + +} diff --git a/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java new file mode 100644 index 0000000000..74f7f1c6b7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java @@ -0,0 +1,70 @@ +package org.kohsuke.github.extras.auth; + +import org.kohsuke.github.*; + +import java.io.IOException; +import java.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Date; + +/** + * This helper class provides an example on how to authenticate a GitHub instance with an installation token, that will + * be automatically refreshed when required. + */ +public class OrgInstallationCredentialProvider implements CredentialProvider { + + private final GitHub gitHub; + + private final String organizationName; + + private String latestToken; + + private Date validUntil; + + public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub) { + this.organizationName = organizationName; + this.gitHub = gitHub; + } + /** + * Obtains a new OAuth2 token, using the configured client to request it. The configured client must be able + * to request the token, this usually means that it needs to have JWT authentication + * + * @throws IOException + * for any problem obtaining the token + */ + @Preview + @Override + @Deprecated + public String getEncodedAuthorization() throws IOException { + if (this.latestToken == null || this.validUntil == null || (new Date()).after(this.validUntil)) { + this.refreshToken(); + } + + return String.format("token %s", this.latestToken); + } + + @Preview + @Deprecated + private void refreshToken() throws IOException { + GHAppInstallation installationByOrganization = this.gitHub.getApp() + .getInstallationByOrganization(this.organizationName); + GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create(); + this.validUntil = ghAppInstallationToken.getExpiresAt(); + this.latestToken = ghAppInstallationToken.getToken(); + } + + public static GitHub getAuthenticatedClient() + throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { + // Build a client that will be used to get Oauth tokens with a JWT token + GitHub jwtAuthenticatedClient = new GitHubBuilder() + .withCredentialProvider(new JWTTokenProvider("12345", Paths.get("~/github-api-app.private-key.der"))) + .build(); + // Build another client (the final one) that will use the Oauth token, and automatically refresh it when + // it is expired. This is the client that can either be further customized, or used directly. + return new GitHubBuilder() + .withCredentialProvider(new OrgInstallationCredentialProvider("myOrganization", jwtAuthenticatedClient)) + .build(); + } + +} From a7112c42df90f5a86be7610f14a9d1ae2ab1b965 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Mon, 5 Oct 2020 13:48:37 +0200 Subject: [PATCH 28/44] linting: JWTTokenProvider.java --- .../java/org/kohsuke/github/extras/auth/JWTTokenProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java index 8e2234a2e6..784299c17c 100644 --- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java +++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java @@ -38,8 +38,8 @@ public JWTTokenProvider(String applicationId, Path keyPath) this.applicationId = applicationId; } - /**add dependencies for a jwt suite - * You can generate a key to load with this method with: + /** + * add dependencies for a jwt suite You can generate a key to load with this method with: * *
      * openssl pkcs8 -topk8 -inform PEM -outform DER -in ~/github-api-app.private-key.pem -out ~/github-api-app.private-key.der -nocrypt

From 610b02968e158e58b2c17183ef3e37a497fe6387 Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Mon, 5 Oct 2020 13:57:52 +0200
Subject: [PATCH 29/44] exlude org.kohsuke.github.extras.auth.* from code
 coverage

This is a package for examples/extra implementations
---
 pom.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pom.xml b/pom.xml
index 09a2ce6439..ff3a55fcfa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,7 @@
                       
                       org.kohsuke.github.extras.okhttp3.ObsoleteUrlFactory.**
                       org.kohsuke.github.extras.okhttp3.ObsoleteUrlFactory
+                      org.kohsuke.github.extras.auth.*
 
                       
                       org.kohsuke.github.example.*

From 43efa7875079a37b81851d22260aaa78ffb232e7 Mon Sep 17 00:00:00 2001
From: Liam Newman 
Date: Tue, 29 Dec 2020 09:29:30 -0800
Subject: [PATCH 30/44] Post-merge fixes

---
 pom.xml                                       | 34 ++++---------------
 .../kohsuke/github/CredentialProvider.java    |  2 +-
 src/main/java/org/kohsuke/github/GitHub.java  |  2 +-
 .../OrgInstallationCredentialProvider.java    |  6 ++--
 4 files changed, 11 insertions(+), 33 deletions(-)

diff --git a/pom.xml b/pom.xml
index 58024bfcdd..702c2fd1bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,7 +45,7 @@
     0.25
     
     false
-    0.11.1
+    0.11.2
   
 
   
@@ -491,20 +491,20 @@
     
       io.jsonwebtoken
       jjwt-api
-      0.11.2
-      test
+      ${jjwt.suite.version}
+      true
     
     
       io.jsonwebtoken
       jjwt-impl
-      0.11.2
-      test
+      ${jjwt.suite.version}
+      true
     
     
       io.jsonwebtoken
       jjwt-jackson
-      0.11.2
-      test
+      ${jjwt.suite.version}
+      true
     
     
       com.squareup.okio
@@ -562,26 +562,6 @@
       2.8.6
       test
     
-    
-    
-      io.jsonwebtoken
-      jjwt-api
-      ${jjwt.suite.version}
-      provided
-    
-    
-      io.jsonwebtoken
-      jjwt-impl
-      ${jjwt.suite.version}
-      provided
-    
-    
-      io.jsonwebtoken
-      jjwt-jackson
-      ${jjwt.suite.version}
-      provided
-    
-    
     
       org.slf4j
       slf4j-simple
diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java
index 6f8ae6833e..45d92f7469 100644
--- a/src/main/java/org/kohsuke/github/CredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/CredentialProvider.java
@@ -15,7 +15,7 @@ public interface CredentialProvider {
     /**
      * Returns the credentials to be used with a given request. As an example, a credential provider for a bearer token
      * will return something like:
-     * 
+     *
      * 
      * {@code
      *  @Override
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index e8c6915737..af5a9a5831 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -87,7 +87,7 @@ public class GitHub {
      * header. Please note that only operations in which permissions have been previously configured and accepted during
      * the GitHub App will be executed successfully.
      * 
-     * 
+     *
      * @param apiUrl
      *            The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
      *            "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For
diff --git a/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java
index 74f7f1c6b7..c1169f0bc5 100644
--- a/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java
@@ -29,11 +29,11 @@ public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub)
     /**
      * Obtains a new OAuth2 token, using the configured client to request it. The configured client must be able
      * to request the token, this usually means that it needs to have JWT authentication
-     * 
+     *
      * @throws IOException
      *             for any problem obtaining the token
      */
-    @Preview
+    @BetaApi
     @Override
     @Deprecated
     public String getEncodedAuthorization() throws IOException {
@@ -44,8 +44,6 @@ public String getEncodedAuthorization() throws IOException {
         return String.format("token %s", this.latestToken);
     }
 
-    @Preview
-    @Deprecated
     private void refreshToken() throws IOException {
         GHAppInstallation installationByOrganization = this.gitHub.getApp()
                 .getInstallationByOrganization(this.organizationName);

From f546cf4521a3b4b9c19de7537108f7abe013b398 Mon Sep 17 00:00:00 2001
From: Liam Newman 
Date: Wed, 30 Dec 2020 09:39:36 -0800
Subject: [PATCH 31/44] Use only credential providers internally to track
 credentials

Removes extra fields from GitHubClient.
---
 .../kohsuke/github/CredentialProvider.java    |  8 ++
 src/main/java/org/kohsuke/github/GitHub.java  | 54 ++++++++++----
 .../org/kohsuke/github/GitHubBuilder.java     | 29 ++------
 .../java/org/kohsuke/github/GitHubClient.java | 53 +++++---------
 .../github/GitHubHttpUrlConnectionClient.java | 15 ++--
 .../github/ImmutableCredentialProvider.java   | 73 ++++++++++++++++---
 .../OrgInstallationCredentialProvider.java    | 43 ++++++++---
 .../OrgInstallationCredentialProvider.java    | 68 -----------------
 .../github/AbstractGitHubWireMockTest.java    |  1 -
 .../kohsuke/github/GitHubConnectionTest.java  | 20 ++---
 ...OrgInstallationCredentialProviderTest.java | 32 +++++---
 11 files changed, 204 insertions(+), 192 deletions(-)
 delete mode 100644 src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java

diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java
index 45d92f7469..6cc5fb4e8c 100644
--- a/src/main/java/org/kohsuke/github/CredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/CredentialProvider.java
@@ -31,6 +31,14 @@ public interface CredentialProvider {
      */
     String getEncodedAuthorization() throws IOException;
 
+    /**
+     * Binds this credential provider to a github instance.
+     *
+     * @param github
+     */
+    default void bind(GitHub github) {
+    }
+
     /**
      * A {@link CredentialProvider} that ensures that no credentials are returned
      */
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index af5a9a5831..cf51c976b1 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -93,32 +93,25 @@ public class GitHub {
      *            "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 in the URL. For
      *            historical reasons, this parameter still accepts the bare domain name, but that's considered
      *            deprecated. Password is also considered deprecated as it is no longer required for api usage.
-     * @param login
-     *            The user ID on GitHub that you are logging in as. Can be omitted if the OAuth token is provided or if
-     *            logging in anonymously. Specifying this would save one API call.
-     * @param oauthAccessToken
-     *            Secret OAuth token.
-     * @param password
-     *            User's password. Always used in conjunction with the {@code login} parameter
      * @param connector
+     *            a connector
+     * @param rateLimitHandler
+     *            rateLimitHandler
+     * @param abuseLimitHandler
+     *            abuseLimitHandler
+     * @param rateLimitChecker
+     *            rateLimitChecker
      * @param credentialProvider
-     *            a credential provider, takes preference over all other auth-related parameters if it's not null
+     *            a credential provider
      */
     GitHub(String apiUrl,
-            String login,
-            String oauthAccessToken,
-            String jwtToken,
-            String password,
             HttpConnector connector,
             RateLimitHandler rateLimitHandler,
             AbuseLimitHandler abuseLimitHandler,
             GitHubRateLimitChecker rateLimitChecker,
             CredentialProvider credentialProvider) throws IOException {
+        credentialProvider.bind(this);
         this.client = new GitHubHttpUrlConnectionClient(apiUrl,
-                login,
-                oauthAccessToken,
-                jwtToken,
-                password,
                 connector,
                 rateLimitHandler,
                 abuseLimitHandler,
@@ -129,6 +122,35 @@ public class GitHub {
         orgs = new ConcurrentHashMap<>();
     }
 
+    private GitHub(GitHubClient client) {
+        this.client = client;
+        users = new ConcurrentHashMap<>();
+        orgs = new ConcurrentHashMap<>();
+    }
+
+    static class CredentialRefreshGitHubWrapper extends GitHub {
+
+        CredentialProvider credentialProvider;
+
+        CredentialRefreshGitHubWrapper(GitHub github, CredentialProvider credentialProvider) {
+            super(github.client);
+            this.credentialProvider = credentialProvider;
+            this.credentialProvider.bind(this);
+        }
+
+        @Nonnull
+        @Override
+        Requester createRequest() {
+            try {
+                // Override
+                return super.createRequest().setHeader("Authorization", credentialProvider.getEncodedAuthorization())
+                        .rateLimit(RateLimitTarget.NONE);
+            } catch (IOException e) {
+                throw new GHException("Failed to create requester to refresh credentials", e);
+            }
+        }
+    }
+
     /**
      * Obtains the credential from "~/.github" or from the System Environment Properties.
      *
diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java
index 4a1ffc7bf9..8fdfc785a7 100644
--- a/src/main/java/org/kohsuke/github/GitHubBuilder.java
+++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java
@@ -24,17 +24,13 @@ public class GitHubBuilder implements Cloneable {
 
     // default scoped so unit tests can read them.
     /* private */ String endpoint = GitHubClient.GITHUB_URL;
-    /* private */ String user;
-    /* private */ String password;
-    /* private */ String oauthToken;
-    /* private */ String jwtToken;
 
     private HttpConnector connector;
 
     private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
     private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT;
     private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker();
-    private CredentialProvider credentialProvider = null;
+    private CredentialProvider credentialProvider = CredentialProvider.ANONYMOUS;
 
     /**
      * Instantiates a new Git hub builder.
@@ -62,13 +58,13 @@ static GitHubBuilder fromCredentials() throws IOException {
 
         builder = fromEnvironment();
 
-        if (builder.oauthToken != null || builder.user != null || builder.jwtToken != null)
+        if (builder.credentialProvider != null)
             return builder;
 
         try {
             builder = fromPropertyFile();
 
-            if (builder.oauthToken != null || builder.user != null || builder.jwtToken != null)
+            if (builder.credentialProvider != null)
                 return builder;
         } catch (FileNotFoundException e) {
             // fall through
@@ -248,9 +244,7 @@ public GitHubBuilder withEndpoint(String endpoint) {
      * @return the git hub builder
      */
     public GitHubBuilder withPassword(String user, String password) {
-        this.user = user;
-        this.password = password;
-        return this;
+        return withCredentialProvider(ImmutableCredentialProvider.fromLoginAndPassword(user, password));
     }
 
     /**
@@ -261,7 +255,7 @@ public GitHubBuilder withPassword(String user, String password) {
      * @return the git hub builder
      */
     public GitHubBuilder withOAuthToken(String oauthToken) {
-        return withOAuthToken(oauthToken, null);
+        return withCredentialProvider(ImmutableCredentialProvider.fromOauthToken(oauthToken));
     }
 
     /**
@@ -274,9 +268,7 @@ public GitHubBuilder withOAuthToken(String oauthToken) {
      * @return the git hub builder
      */
     public GitHubBuilder withOAuthToken(String oauthToken, String user) {
-        this.oauthToken = oauthToken;
-        this.user = user;
-        return this;
+        return withCredentialProvider(ImmutableCredentialProvider.fromOauthToken(oauthToken, user));
     }
 
     public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) {
@@ -293,7 +285,7 @@ public GitHubBuilder withCredentialProvider(final CredentialProvider credentialP
      * @see GHAppInstallation#createToken(java.util.Map) GHAppInstallation#createToken(java.util.Map)
      */
     public GitHubBuilder withAppInstallationToken(String appInstallationToken) {
-        return withOAuthToken(appInstallationToken, "");
+        return withCredentialProvider(ImmutableCredentialProvider.fromAppInstallationToken(appInstallationToken));
     }
 
     /**
@@ -304,8 +296,7 @@ public GitHubBuilder withAppInstallationToken(String appInstallationToken) {
      * @return the git hub builder
      */
     public GitHubBuilder withJwtToken(String jwtToken) {
-        this.jwtToken = jwtToken;
-        return this;
+        return withCredentialProvider(ImmutableCredentialProvider.fromJwtToken(jwtToken));
     }
 
     /**
@@ -427,10 +418,6 @@ public GitHubBuilder withProxy(final Proxy p) {
      */
     public GitHub build() throws IOException {
         return new GitHub(endpoint,
-                user,
-                oauthToken,
-                jwtToken,
-                password,
                 connector,
                 rateLimitHandler,
                 abuseLimitHandler,
diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java
index 963fc3f886..dee9f83113 100644
--- a/src/main/java/org/kohsuke/github/GitHubClient.java
+++ b/src/main/java/org/kohsuke/github/GitHubClient.java
@@ -3,7 +3,6 @@
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
 import org.apache.commons.io.IOUtils;
-import org.jetbrains.annotations.Nullable;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -72,10 +71,6 @@ abstract class GitHubClient {
     }
 
     GitHubClient(String apiUrl,
-            String login,
-            String oauthAccessToken,
-            String jwtToken,
-            String password,
             HttpConnector connector,
             RateLimitHandler rateLimitHandler,
             AbuseLimitHandler abuseLimitHandler,
@@ -94,42 +89,35 @@ abstract class GitHubClient {
         this.connector = connector;
 
         // Prefer credential configuration via provider
-        if (credentialProvider != null) {
-            this.credentialProvider = credentialProvider;
-        } else {
-            if (oauthAccessToken != null) {
-                this.credentialProvider = ImmutableCredentialProvider.fromOauthToken(oauthAccessToken);
-            } else {
-                if (jwtToken != null) {
-                    this.credentialProvider = ImmutableCredentialProvider.fromJwtToken(jwtToken);
-                } else if (password != null) {
-                    this.credentialProvider = ImmutableCredentialProvider.fromLoginAndPassword(login, password);
-                } else {// anonymous access
-                    this.credentialProvider = CredentialProvider.ANONYMOUS;
-                }
-            }
-        }
+        this.credentialProvider = credentialProvider;
 
         this.rateLimitHandler = rateLimitHandler;
         this.abuseLimitHandler = abuseLimitHandler;
         this.rateLimitChecker = rateLimitChecker;
 
-        this.login = getCurrentUser(login, jwtToken, myselfConsumer);
+        this.login = getCurrentUser(myselfConsumer);
     }
 
-    @Nullable
-    private String getCurrentUser(String login, String jwtToken, Consumer myselfConsumer) throws IOException {
-        if (login == null && this.credentialProvider.getEncodedAuthorization() != null && jwtToken == null) {
-            try {
-                GHMyself myself = fetch(GHMyself.class, "/user");
-                if (myselfConsumer != null) {
-                    myselfConsumer.accept(myself);
+    private String getCurrentUser(Consumer myselfConsumer) throws IOException {
+        String login = null;
+        if (this.credentialProvider instanceof ImmutableCredentialProvider.UserCredentialProvider
+                && this.credentialProvider.getEncodedAuthorization() != null) {
+
+            ImmutableCredentialProvider.UserCredentialProvider userCredentialProvider = (ImmutableCredentialProvider.UserCredentialProvider) this.credentialProvider;
+
+            login = userCredentialProvider.getLogin();
+
+            if (login == null) {
+                try {
+                    GHMyself myself = fetch(GHMyself.class, "/user");
+                    if (myselfConsumer != null) {
+                        myselfConsumer.accept(myself);
+                    }
+                    login = myself.getLogin();
+                } catch (IOException e) {
+                    return null;
                 }
-                return myself.getLogin();
-            } catch (IOException e) {
-                return null;
             }
-
         }
         return login;
     }
@@ -394,7 +382,6 @@ public  GitHubResponse sendRequest(GitHubRequest request, @CheckForNull Gi
                                 "GitHub API request [" + (login == null ? "anonymous" : login) + "]: "
                                         + request.method() + " " + request.url().toString());
                     }
-
                     rateLimitChecker.checkRateLimit(this, request);
 
                     responseInfo = getResponseInfo(request);
diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
index b8f5ee840c..5726412598 100644
--- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
+++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
@@ -33,10 +33,6 @@
 class GitHubHttpUrlConnectionClient extends GitHubClient {
 
     GitHubHttpUrlConnectionClient(String apiUrl,
-            String login,
-            String oauthAccessToken,
-            String jwtToken,
-            String password,
             HttpConnector connector,
             RateLimitHandler rateLimitHandler,
             AbuseLimitHandler abuseLimitHandler,
@@ -44,10 +40,6 @@ class GitHubHttpUrlConnectionClient extends GitHubClient {
             Consumer myselfConsumer,
             CredentialProvider credentialProvider) throws IOException {
         super(apiUrl,
-                login,
-                oauthAccessToken,
-                jwtToken,
-                password,
                 connector,
                 rateLimitHandler,
                 abuseLimitHandler,
@@ -116,8 +108,11 @@ static HttpURLConnection setupConnection(@Nonnull GitHubClient client, @Nonnull
 
             // if the authentication is needed but no credential is given, try it anyway (so that some calls
             // that do work with anonymous access in the reduced form should still work.)
-            if (client.credentialProvider.getEncodedAuthorization() != null) {
-                connection.setRequestProperty("Authorization", client.credentialProvider.getEncodedAuthorization());
+            if (!request.headers().containsKey("Authorization")) {
+                String authorization = client.credentialProvider.getEncodedAuthorization();
+                if (authorization != null) {
+                    connection.setRequestProperty("Authorization", client.credentialProvider.getEncodedAuthorization());
+                }
             }
 
             setRequestMethod(request.method(), connection);
diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java
index 69f54331cf..e0cdc54cb2 100644
--- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java
@@ -4,6 +4,8 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 
+import javax.annotation.CheckForNull;
+
 /**
  * A {@link CredentialProvider} that always returns the same credentials
  */
@@ -24,7 +26,30 @@ public ImmutableCredentialProvider(String authorization) {
      *         oauthAccessToken
      */
     public static CredentialProvider fromOauthToken(String oauthAccessToken) {
-        return new ImmutableCredentialProvider(String.format("token %s", oauthAccessToken));
+        return new UserCredentialProvider(String.format("token %s", oauthAccessToken));
+    }
+
+    /**
+     * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken
+     *
+     * @param oauthAccessToken
+     *            The token
+     * @return a correctly configured {@link CredentialProvider} that will always return the same provided
+     *         oauthAccessToken
+     */
+    public static CredentialProvider fromOauthToken(String oauthAccessToken, String login) {
+        return new UserCredentialProvider(String.format("token %s", oauthAccessToken), login);
+    }
+
+    /**
+     * Builds and returns a {@link CredentialProvider} from a given App Installation Token
+     *
+     * @param appInstallationToken
+     *            A string containing the GitHub App installation token
+     * @return the configured Builder from given GitHub App installation token.
+     */
+    public static CredentialProvider fromAppInstallationToken(String appInstallationToken) {
+        return fromOauthToken(appInstallationToken, "");
     }
 
     /**
@@ -47,20 +72,48 @@ public static CredentialProvider fromJwtToken(String jwtToken) {
      *            The password for the associated user
      * @return a correctly configured {@link CredentialProvider} that will always return the credentials for the same
      *         user and password combo
-     * @throws UnsupportedEncodingException
-     *             the character encoding is not supported
+     * @deprecated Login with password credentials are no longer supported by GitHub
      */
-    public static CredentialProvider fromLoginAndPassword(String login, String password)
-            throws UnsupportedEncodingException {
-        String authorization = (String.format("%s:%s", login, password));
-        String charsetName = StandardCharsets.UTF_8.name();
-        String b64encoded = Base64.getEncoder().encodeToString(authorization.getBytes(charsetName));
-        String encodedAuthorization = String.format("Basic %s", b64encoded);
-        return new ImmutableCredentialProvider(encodedAuthorization);
+    @Deprecated
+    public static CredentialProvider fromLoginAndPassword(String login, String password) {
+        try {
+            String authorization = (String.format("%s:%s", login, password));
+            String charsetName = StandardCharsets.UTF_8.name();
+            String b64encoded = Base64.getEncoder().encodeToString(authorization.getBytes(charsetName));
+            String encodedAuthorization = String.format("Basic %s", b64encoded);
+            return new UserCredentialProvider(encodedAuthorization, login);
+        } catch (UnsupportedEncodingException e) {
+            // If UTF-8 isn't supported, there are bigger problems
+            throw new IllegalStateException("Could not generate encoded authorization", e);
+        }
     }
 
     @Override
     public String getEncodedAuthorization() {
         return this.authorization;
     }
+
+    /**
+     * An internal class representing all user-related credentials, which are credentials that have a login or should
+     * query the user endpoint for the login matching this credential.
+     */
+    static class UserCredentialProvider extends ImmutableCredentialProvider {
+
+        private final String login;
+
+        UserCredentialProvider(String authorization) {
+            this(authorization, null);
+        }
+
+        UserCredentialProvider(String authorization, String login) {
+            super(authorization);
+            this.login = login;
+        }
+
+        @CheckForNull
+        String getLogin() {
+            return login;
+        }
+
+    }
 }
diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java
index 34fc05f0e2..56f8bcd0ca 100644
--- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java
@@ -1,19 +1,27 @@
 package org.kohsuke.github;
 
 import java.io.IOException;
-import java.util.Date;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
 
 /**
  * Provides a CredentialProvider that performs automatic token refresh.
  */
 public class OrgInstallationCredentialProvider implements CredentialProvider {
 
-    private final GitHub gitHub;
+    private GitHub baseGitHub;
+    private GitHub gitHub;
 
+    private final CredentialProvider refreshProvider;
     private final String organizationName;
 
     private String latestToken;
-    private Date validUntil;
+
+    @Nonnull
+    private Instant validUntil = Instant.MIN;
 
     /**
      * Provides a CredentialProvider that performs automatic token refresh, based on an previously authenticated github
@@ -21,27 +29,38 @@ public class OrgInstallationCredentialProvider implements CredentialProvider {
      *
      * @param organizationName
      *            The name of the organization where the application is installed
-     * @param gitHub
-     *            A GitHub client that must be configured with a valid JWT token
      */
-    public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub) {
+    @BetaApi
+    @Deprecated
+    public OrgInstallationCredentialProvider(String organizationName, CredentialProvider credentialProvider) {
         this.organizationName = organizationName;
-        this.gitHub = gitHub;
+        this.refreshProvider = credentialProvider;
+    }
+
+    @Override
+    public void bind(GitHub github) {
+        this.baseGitHub = github;
     }
 
     @Override
     public String getEncodedAuthorization() throws IOException {
-        if (latestToken == null || validUntil == null || new Date().after(this.validUntil)) {
-            refreshToken();
+        synchronized (this) {
+            if (latestToken == null || Instant.now().isAfter(this.validUntil)) {
+                refreshToken();
+            }
+            return String.format("token %s", latestToken);
         }
-        return String.format("token %s", latestToken);
     }
 
     private void refreshToken() throws IOException {
+        if (gitHub == null) {
+            gitHub = new GitHub.CredentialRefreshGitHubWrapper(this.baseGitHub, refreshProvider);
+        }
+
         GHAppInstallation installationByOrganization = gitHub.getApp()
                 .getInstallationByOrganization(this.organizationName);
         GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create();
-        this.validUntil = ghAppInstallationToken.getExpiresAt();
-        this.latestToken = ghAppInstallationToken.getToken();
+        this.validUntil = ghAppInstallationToken.getExpiresAt().toInstant().minus(Duration.ofMinutes(5));
+        this.latestToken = Objects.requireNonNull(ghAppInstallationToken.getToken());
     }
 }
diff --git a/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java
deleted file mode 100644
index c1169f0bc5..0000000000
--- a/src/main/java/org/kohsuke/github/extras/auth/OrgInstallationCredentialProvider.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.kohsuke.github.extras.auth;
-
-import org.kohsuke.github.*;
-
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Date;
-
-/**
- * This helper class provides an example on how to authenticate a GitHub instance with an installation token, that will
- * be automatically refreshed when required.
- */
-public class OrgInstallationCredentialProvider implements CredentialProvider {
-
-    private final GitHub gitHub;
-
-    private final String organizationName;
-
-    private String latestToken;
-
-    private Date validUntil;
-
-    public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub) {
-        this.organizationName = organizationName;
-        this.gitHub = gitHub;
-    }
-    /**
-     * Obtains a new OAuth2 token, using the configured client to request it. The configured client must be able
-     * to request the token, this usually means that it needs to have JWT authentication
-     *
-     * @throws IOException
-     *             for any problem obtaining the token
-     */
-    @BetaApi
-    @Override
-    @Deprecated
-    public String getEncodedAuthorization() throws IOException {
-        if (this.latestToken == null || this.validUntil == null || (new Date()).after(this.validUntil)) {
-            this.refreshToken();
-        }
-
-        return String.format("token %s", this.latestToken);
-    }
-
-    private void refreshToken() throws IOException {
-        GHAppInstallation installationByOrganization = this.gitHub.getApp()
-                .getInstallationByOrganization(this.organizationName);
-        GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create();
-        this.validUntil = ghAppInstallationToken.getExpiresAt();
-        this.latestToken = ghAppInstallationToken.getToken();
-    }
-
-    public static GitHub getAuthenticatedClient()
-            throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
-        // Build a client that will be used to get Oauth tokens with a JWT token
-        GitHub jwtAuthenticatedClient = new GitHubBuilder()
-                .withCredentialProvider(new JWTTokenProvider("12345", Paths.get("~/github-api-app.private-key.der")))
-                .build();
-        // Build another client (the final one) that will use the Oauth token, and automatically refresh it when
-        // it is expired. This is the client that can either be further customized, or used directly.
-        return new GitHubBuilder()
-                .withCredentialProvider(new OrgInstallationCredentialProvider("myOrganization", jwtAuthenticatedClient))
-                .build();
-    }
-
-}
diff --git a/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java b/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java
index 8cede300c7..84b838ac41 100644
--- a/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java
+++ b/src/test/java/org/kohsuke/github/AbstractGitHubWireMockTest.java
@@ -100,7 +100,6 @@ protected GitHubBuilder getGitHubBuilder() {
             // This sets the user and password to a placeholder for wiremock testing
             // This makes the tests believe they are running with permissions
             // The recorded stubs will behave like they running with permissions
-            builder.oauthToken = null;
             builder.withPassword(STUBBED_USER_LOGIN, STUBBED_USER_PASSWORD);
         }
 
diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
index 4366d10f13..a1c5aead23 100644
--- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
+++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
@@ -65,10 +65,11 @@ public void testGitHubBuilderFromEnvironment() throws IOException {
 
         GitHubBuilder builder = GitHubBuilder.fromEnvironment();
 
-        assertEquals("bogus", builder.user);
-        assertEquals("bogus", builder.oauthToken);
-        assertEquals("bogus", builder.password);
-        assertEquals("bogus", builder.jwtToken);
+        // TODO: figure out how to test these again
+        // assertEquals("bogus", builder.user);
+        // assertEquals("bogus", builder.oauthToken);
+        // assertEquals("bogus", builder.password);
+        // assertEquals("bogus", builder.jwtToken);
 
     }
 
@@ -86,17 +87,18 @@ public void testGitHubBuilderFromCustomEnvironment() throws IOException {
         GitHubBuilder builder = GitHubBuilder
                 .fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint");
 
-        assertEquals("bogusLogin", builder.user);
-        assertEquals("bogusOauth", builder.oauthToken);
-        assertEquals("bogusPassword", builder.password);
+        // TODO: figure out how to test these again
+        // assertEquals("bogusLogin", builder.user);
+        // assertEquals("bogusOauth", builder.oauthToken);
+        // assertEquals("bogusPassword", builder.password);
         assertEquals("bogusEndpoint", builder.endpoint);
     }
 
     @Test
     public void testGithubBuilderWithAppInstallationToken() throws Exception {
         GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus");
-        assertEquals("bogus", builder.oauthToken);
-        assertEquals("", builder.user);
+        // assertEquals("bogus", builder.oauthToken);
+        // assertEquals("", builder.user);
 
         // test authorization header is set as in the RFC6749
         GitHub github = builder.build();
diff --git a/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java b/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java
index dd65c43b1b..032aed44aa 100644
--- a/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java
+++ b/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java
@@ -1,32 +1,40 @@
 package org.kohsuke.github;
 
-import net.sf.ezmorph.test.ArrayAssertions;
 import org.junit.Test;
 
 import java.io.IOException;
 
-public class OrgInstallationCredentialProviderTest extends AbstractGitHubWireMockTest {
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
 
-    @Test(expected = HttpException.class)
-    public void invalidJWTTokenRaisesException() throws IOException {
+public class OrgInstallationCredentialProviderTest extends AbstractGHAppInstallationTest {
 
-        gitHub.getClient().credentialProvider = ImmutableCredentialProvider.fromJwtToken("myToken");
+    public OrgInstallationCredentialProviderTest() {
+        useDefaultGitHub = false;
+    }
 
-        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("testOrganization", gitHub);
+    @Test(expected = HttpException.class)
+    public void invalidJWTTokenRaisesException() throws IOException {
+        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("testOrganization",
+                ImmutableCredentialProvider.fromJwtToken("myToken"));
+        gitHub = getGitHubBuilder().withCredentialProvider(provider)
+                .withEndpoint(mockGitHub.apiServer().baseUrl())
+                .build();
 
         provider.getEncodedAuthorization();
     }
 
     @Test
     public void validJWTTokenAllowsOauthTokenRequest() throws IOException {
-        gitHub.getClient().credentialProvider = ImmutableCredentialProvider.fromJwtToken("valid-token");
-
-        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("hub4j-test-org", gitHub);
-
+        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("hub4j-test-org",
+                ImmutableCredentialProvider.fromJwtToken("bogus-valid-token"));
+        gitHub = getGitHubBuilder().withCredentialProvider(provider)
+                .withEndpoint(mockGitHub.apiServer().baseUrl())
+                .build();
         String encodedAuthorization = provider.getEncodedAuthorization();
 
-        ArrayAssertions.assertNotNull(encodedAuthorization);
-        ArrayAssertions.assertEquals("token v1.9a12d913f980a45a16ac9c3a9d34d9b7sa314cb6", encodedAuthorization);
+        assertThat(encodedAuthorization, notNullValue());
+        assertThat(encodedAuthorization, equalTo("token v1.9a12d913f980a45a16ac9c3a9d34d9b7sa314cb6"));
     }
 
 }

From a9438b612158b2ef432a8ff647c9e14c374a73c3 Mon Sep 17 00:00:00 2001
From: Liam Newman 
Date: Wed, 30 Dec 2020 10:46:45 -0800
Subject: [PATCH 32/44] Move tests to use JWTTokenProvider

---
 pom.xml                                       |   1 -
 .../github/extras/auth/JWTTokenProvider.java  | 101 +++++++++++++-----
 .../github/AbstractGHAppInstallationTest.java |  26 ++++-
 3 files changed, 98 insertions(+), 30 deletions(-)

diff --git a/pom.xml b/pom.xml
index 702c2fd1bb..416ac8d880 100644
--- a/pom.xml
+++ b/pom.xml
@@ -149,7 +149,6 @@
                       
                       org.kohsuke.github.extras.okhttp3.ObsoleteUrlFactory.**
                       org.kohsuke.github.extras.okhttp3.ObsoleteUrlFactory
-                      org.kohsuke.github.extras.auth.*
 
                       
                       org.kohsuke.github.example.*
diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
index 784299c17c..28e0291969 100644
--- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
@@ -5,17 +5,23 @@
 import io.jsonwebtoken.SignatureAlgorithm;
 import org.kohsuke.github.CredentialProvider;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.time.Duration;
+import java.time.Instant;
+import java.util.Base64;
 import java.util.Date;
 
+import javax.annotation.Nonnull;
+
 /**
  * A credential provider that gives valid JWT tokens. These tokens are then used to create a time-based token to
  * authenticate as an application. This token provider does not provide any kind of caching, and will always request a
@@ -27,57 +33,100 @@ public class JWTTokenProvider implements CredentialProvider {
 
     private final PrivateKey privateKey;
 
+    @Nonnull
+    private Instant validUntil = Instant.MIN;
+
+    private String token;
+
     /**
      * The identifier for the application
      */
     private final String applicationId;
 
-    public JWTTokenProvider(String applicationId, Path keyPath)
-            throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
-        this.privateKey = loadPrivateKey(keyPath);
+    public JWTTokenProvider(String applicationId, File keyFile) throws GeneralSecurityException, IOException {
+        this(applicationId, loadPrivateKey(keyFile.toPath()));
+    }
+
+    public JWTTokenProvider(String applicationId, Path keyPath) throws GeneralSecurityException, IOException {
+        this(applicationId, loadPrivateKey(keyPath));
+    }
+
+    public JWTTokenProvider(String applicationId, PrivateKey privateKey) {
+        this.privateKey = privateKey;
         this.applicationId = applicationId;
     }
 
+    @Override
+    public String getEncodedAuthorization() throws IOException {
+        synchronized (this) {
+            if (Instant.now().isAfter(validUntil)) {
+                token = refreshJWT();
+            }
+            return token;
+        }
+    }
+
     /**
-     * add dependencies for a jwt suite You can generate a key to load with this method with:
+     * add dependencies for a jwt suite You can generate a key to load in this method with:
      *
      * 
      * openssl pkcs8 -topk8 -inform PEM -outform DER -in ~/github-api-app.private-key.pem -out ~/github-api-app.private-key.der -nocrypt
      * 
*/ - private PrivateKey loadPrivateKey(Path keyPath) - throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { + private static PrivateKey loadPrivateKey(Path keyPath) throws GeneralSecurityException, IOException { + String keyString = new String(Files.readAllBytes(keyPath), StandardCharsets.UTF_8); + return getPrivateKeyFromString(keyString); + } + + /** + * Convert a PKCS#8 formatted private key in string format into a java PrivateKey + * + * @param key + * PCKS#8 string + * @return private key + * @throws GeneralSecurityException + * if we couldn't parse the string + */ + private static PrivateKey getPrivateKeyFromString(final String key) throws GeneralSecurityException { + if (key.contains(" RSA ")) { + throw new InvalidKeySpecException( + "Private key must be a PKCS#8 formatted string, to convert it from PKCS#1 use: " + + "openssl pkcs8 -topk8 -inform PEM -outform PEM -in current-key.pem -out new-key.pem -nocrypt"); + } + + // Remove all comments and whitespace from PEM + // such as "-----BEGIN PRIVATE KEY-----" and newlines + String privateKeyContent = key.replaceAll("(?m)^--.*", "").replaceAll("\\s", ""); - byte[] keyBytes = Files.readAllBytes(keyPath); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); - return kf.generatePrivate(spec); + + try { + byte[] decode = Base64.getDecoder().decode(privateKeyContent); + PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(decode); + + return kf.generatePrivate(keySpecPKCS8); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Failed to decode private key: " + e.getMessage(), e); + } } - public String getJWT() { - long nowMillis = System.currentTimeMillis(); - Date now = new Date(nowMillis); + private String refreshJWT() { + Instant now = Instant.now(); + + // Token expires in 10 minutes + Instant expiration = Instant.now().plus(Duration.ofMinutes(10)); // Let's set the JWT Claims JwtBuilder builder = Jwts.builder() - .setIssuedAt(now) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(expiration)) .setIssuer(this.applicationId) .signWith(privateKey, SignatureAlgorithm.RS256); - // if it has been specified, let's add the expiration - if (MINUTES_10 > 0) { - long expMillis = nowMillis + MINUTES_10; - Date exp = new Date(expMillis); - builder.setExpiration(exp); - } + // Token will refresh after 8 minutes + validUntil = expiration.minus(Duration.ofMinutes(2)); // Builds the JWT and serializes it to a compact, URL-safe string return builder.compact(); } - - @Override - public String getEncodedAuthorization() throws IOException { - return getJWT(); - } - } diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java index b25d13bc55..ea989d65a8 100644 --- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java @@ -2,8 +2,11 @@ import io.jsonwebtoken.Jwts; import org.apache.commons.io.IOUtils; +import org.kohsuke.github.extras.auth.JWTTokenProvider; +import java.io.File; import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; @@ -21,6 +24,23 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest { private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem"; private static String PRIVATE_KEY_FILE_APP_3 = "/ghapi-test-app-3.private-key.pem"; + private static CredentialProvider JWT_PROVIDER_1; + private static CredentialProvider JWT_PROVIDER_2; + private static CredentialProvider JWT_PROVIDER_3; + + AbstractGHAppInstallationTest() { + try { + JWT_PROVIDER_1 = new JWTTokenProvider(TEST_APP_ID_1, + new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_1).getFile())); + JWT_PROVIDER_2 = new JWTTokenProvider(TEST_APP_ID_2, + new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile())); + JWT_PROVIDER_3 = new JWTTokenProvider(TEST_APP_ID_3, + new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_3).getFile())); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("These should never fail", e); + } + } + private String createJwtToken(String keyFileResouceName, String appId) { try { String keyPEM = IOUtils.toString(this.getClass().getResource(keyFileResouceName), "US-ASCII") @@ -63,15 +83,15 @@ private GHAppInstallation getAppInstallationWithToken(String jwtToken) throws IO } protected GHAppInstallation getAppInstallationWithTokenApp1() throws IOException { - return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_1, TEST_APP_ID_1)); + return getAppInstallationWithToken(JWT_PROVIDER_1.getEncodedAuthorization()); } protected GHAppInstallation getAppInstallationWithTokenApp2() throws IOException { - return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_2, TEST_APP_ID_2)); + return getAppInstallationWithToken(JWT_PROVIDER_2.getEncodedAuthorization()); } protected GHAppInstallation getAppInstallationWithTokenApp3() throws IOException { - return getAppInstallationWithToken(createJwtToken(PRIVATE_KEY_FILE_APP_3, TEST_APP_ID_3)); + return getAppInstallationWithToken(JWT_PROVIDER_3.getEncodedAuthorization()); } } From bd39b07bb5f062dcb07405dc58e6ae1c7bd2f1a7 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Wed, 30 Dec 2020 14:07:37 -0800 Subject: [PATCH 33/44] Fix javadoc issues --- src/main/java/org/kohsuke/github/CredentialProvider.java | 3 +++ .../java/org/kohsuke/github/ImmutableCredentialProvider.java | 3 +++ .../org/kohsuke/github/OrgInstallationCredentialProvider.java | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/CredentialProvider.java index 6cc5fb4e8c..ea29f3e9b1 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/CredentialProvider.java @@ -34,7 +34,10 @@ public interface CredentialProvider { /** * Binds this credential provider to a github instance. * + * Only needs to be implemented by dynamic credentials providers that use a github instance in order to refresh. + * * @param github + * The github instance to be used for refreshing dynamic credentials */ default void bind(GitHub github) { } diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java index e0cdc54cb2..d7f5815f97 100644 --- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java @@ -34,6 +34,9 @@ public static CredentialProvider fromOauthToken(String oauthAccessToken) { * * @param oauthAccessToken * The token + * @param login + * The login for this token + * * @return a correctly configured {@link CredentialProvider} that will always return the same provided * oauthAccessToken */ diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java index 56f8bcd0ca..f4a3bc7c11 100644 --- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java +++ b/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java @@ -29,6 +29,9 @@ public class OrgInstallationCredentialProvider implements CredentialProvider { * * @param organizationName * The name of the organization where the application is installed + * @param credentialProvider + * A credential provider that returns a JWT token that can be used to refresh the App Installation token + * from GitHub. */ @BetaApi @Deprecated From 66704460373b97162960812c39b44be9aee63c3d Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Wed, 30 Dec 2020 15:51:27 -0800 Subject: [PATCH 34/44] Reenable GitHubBuilder tests --- src/main/java/org/kohsuke/github/GitHub.java | 2 +- .../org/kohsuke/github/GitHubBuilder.java | 29 +++++- .../java/org/kohsuke/github/GitHubClient.java | 7 +- .../github/GitHubHttpUrlConnectionClient.java | 4 +- .../kohsuke/github/GitHubConnectionTest.java | 95 ++++++++++++++----- 5 files changed, 104 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index cf51c976b1..05b3d32f83 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -130,7 +130,7 @@ private GitHub(GitHubClient client) { static class CredentialRefreshGitHubWrapper extends GitHub { - CredentialProvider credentialProvider; + private final CredentialProvider credentialProvider; CredentialRefreshGitHubWrapper(GitHub github, CredentialProvider credentialProvider) { super(github.client); diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 8fdfc785a7..72c5984ea0 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -30,7 +30,7 @@ public class GitHubBuilder implements Cloneable { private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT; private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker(); - private CredentialProvider credentialProvider = CredentialProvider.ANONYMOUS; + /* private */ CredentialProvider credentialProvider = CredentialProvider.ANONYMOUS; /** * Instantiates a new Git hub builder. @@ -212,9 +212,20 @@ public static GitHubBuilder fromPropertyFile(String propertyFileName) throws IOE */ public static GitHubBuilder fromProperties(Properties props) { GitHubBuilder self = new GitHubBuilder(); - self.withOAuthToken(props.getProperty("oauth"), props.getProperty("login")); - self.withJwtToken(props.getProperty("jwt")); - self.withPassword(props.getProperty("login"), props.getProperty("password")); + String oauth = props.getProperty("oauth"); + String jwt = props.getProperty("jwt"); + String login = props.getProperty("login"); + String password = props.getProperty("password"); + + if (oauth != null) { + self.withOAuthToken(oauth, login); + } + if (jwt != null) { + self.withJwtToken(jwt); + } + if (password != null) { + self.withPassword(login, password); + } self.withEndpoint(props.getProperty("endpoint", GitHubClient.GITHUB_URL)); return self; } @@ -271,6 +282,16 @@ public GitHubBuilder withOAuthToken(String oauthToken, String user) { return withCredentialProvider(ImmutableCredentialProvider.fromOauthToken(oauthToken, user)); } + /** + * Configures a {@link CredentialProvider} for this builder + * + * There can be only one credential provider per client instance. + * + * @param credentialProvider + * the credential provider + * @return the git hub builder + * + */ public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) { this.credentialProvider = credentialProvider; return this; diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index dee9f83113..faff6f812e 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -46,7 +46,7 @@ abstract class GitHubClient { protected final RateLimitHandler rateLimitHandler; protected final AbuseLimitHandler abuseLimitHandler; private final GitHubRateLimitChecker rateLimitChecker; - CredentialProvider credentialProvider; + private final CredentialProvider credentialProvider; private HttpConnector connector; @@ -212,6 +212,11 @@ public GHRateLimit getRateLimit() throws IOException { return getRateLimit(RateLimitTarget.NONE); } + @CheckForNull + protected String getEncodedAuthorization() throws IOException { + return credentialProvider.getEncodedAuthorization(); + } + @Nonnull GHRateLimit getRateLimit(@Nonnull RateLimitTarget rateLimitTarget) throws IOException { GHRateLimit result; diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java index 5726412598..ed130990ec 100644 --- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java +++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java @@ -109,9 +109,9 @@ static HttpURLConnection setupConnection(@Nonnull GitHubClient client, @Nonnull // if the authentication is needed but no credential is given, try it anyway (so that some calls // that do work with anonymous access in the reduced form should still work.) if (!request.headers().containsKey("Authorization")) { - String authorization = client.credentialProvider.getEncodedAuthorization(); + String authorization = client.getEncodedAuthorization(); if (authorization != null) { - connection.setRequestProperty("Authorization", client.credentialProvider.getEncodedAuthorization()); + connection.setRequestProperty("Authorization", client.getEncodedAuthorization()); } } diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java index a1c5aead23..9f5a9a3e51 100644 --- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java +++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java @@ -6,6 +6,8 @@ import java.lang.reflect.Field; import java.util.*; +import static org.hamcrest.CoreMatchers.*; + /** * Unit test for {@link GitHub}. */ @@ -56,20 +58,44 @@ public void testGitHubBuilderFromEnvironment() throws IOException { Map props = new HashMap(); - props.put("login", "bogus"); - props.put("oauth", "bogus"); - props.put("password", "bogus"); - props.put("jwt", "bogus"); + props.put("endpoint", "bogus endpoint url"); + props.put("oauth", "bogus oauth token string"); + setupEnvironment(props); + GitHubBuilder builder = GitHubBuilder.fromEnvironment(); + + assertThat(builder.endpoint, equalTo("bogus endpoint url")); + + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + nullValue()); + props.put("login", "bogus login"); setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment(); - GitHubBuilder builder = GitHubBuilder.fromEnvironment(); + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + equalTo("bogus login")); + + props.put("jwt", "bogus jwt token string"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment(); + + assertThat(builder.credentialProvider, + not(instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class))); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("Bearer bogus jwt token string")); + + props.put("password", "bogus weak password"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment(); - // TODO: figure out how to test these again - // assertEquals("bogus", builder.user); - // assertEquals("bogus", builder.oauthToken); - // assertEquals("bogus", builder.password); - // assertEquals("bogus", builder.jwtToken); + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), + equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA==")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + equalTo("bogus login")); } @@ -77,33 +103,52 @@ public void testGitHubBuilderFromEnvironment() throws IOException { public void testGitHubBuilderFromCustomEnvironment() throws IOException { Map props = new HashMap(); - props.put("customLogin", "bogusLogin"); - props.put("customOauth", "bogusOauth"); - props.put("customPassword", "bogusPassword"); - props.put("customEndpoint", "bogusEndpoint"); - + props.put("customEndpoint", "bogus endpoint url"); + props.put("customOauth", "bogus oauth token string"); setupEnvironment(props); - GitHubBuilder builder = GitHubBuilder .fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint"); - // TODO: figure out how to test these again - // assertEquals("bogusLogin", builder.user); - // assertEquals("bogusOauth", builder.oauthToken); - // assertEquals("bogusPassword", builder.password); - assertEquals("bogusEndpoint", builder.endpoint); + assertThat(builder.endpoint, equalTo("bogus endpoint url")); + + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + nullValue()); + + props.put("customLogin", "bogus login"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint"); + + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + equalTo("bogus login")); + + props.put("customPassword", "bogus weak password"); + setupEnvironment(props); + builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint"); + + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), + equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA==")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + equalTo("bogus login")); } @Test public void testGithubBuilderWithAppInstallationToken() throws Exception { - GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus"); - // assertEquals("bogus", builder.oauthToken); - // assertEquals("", builder.user); + + GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus app token"); + assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)); + assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus app token")); + assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(), + equalTo("")); // test authorization header is set as in the RFC6749 GitHub github = builder.build(); // change this to get a request - assertEquals("token bogus", github.getClient().credentialProvider.getEncodedAuthorization()); + assertEquals("token bogus app token", github.getClient().getEncodedAuthorization()); assertEquals("", github.getClient().login); } From f6ac4d355961573092f446920066a24727a6c607 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Thu, 7 Jan 2021 09:46:30 +0100 Subject: [PATCH 35/44] rename: credential provider -> authorization provider This includes renames in comments, related methods, javadocs and fields/variables. --- ...ovider.java => AuthorizationProvider.java} | 16 +++--- src/main/java/org/kohsuke/github/GitHub.java | 20 +++---- .../org/kohsuke/github/GitHubBuilder.java | 30 +++++----- .../java/org/kohsuke/github/GitHubClient.java | 19 ++++--- .../github/GitHubHttpUrlConnectionClient.java | 4 +- ...va => ImmutableAuthorizationProvider.java} | 48 ++++++++-------- ...OrgInstallationAuthorizationProvider.java} | 20 +++---- .../github/extras/auth/JWTTokenProvider.java | 6 +- .../github/AbstractGHAppInstallationTest.java | 6 +- .../kohsuke/github/GitHubConnectionTest.java | 55 ++++++++----------- ...nstallationAuthorizationProviderTest.java} | 16 +++--- .../mappings/app-2.json | 0 .../mappings/user-1.json | 0 .../__files/app-2.json | 0 .../orgs_hub4j-test-org_installation-3.json | 0 .../mappings/app-2.json | 0 ...nstallations_11575015_access_tokens-4.json | 0 .../orgs_hub4j-test-org_installation-3.json | 0 .../mappings/user-1.json | 0 19 files changed, 117 insertions(+), 123 deletions(-) rename src/main/java/org/kohsuke/github/{CredentialProvider.java => AuthorizationProvider.java} (71%) rename src/main/java/org/kohsuke/github/{ImmutableCredentialProvider.java => ImmutableAuthorizationProvider.java} (53%) rename src/main/java/org/kohsuke/github/{OrgInstallationCredentialProvider.java => OrgInstallationAuthorizationProvider.java} (69%) rename src/test/java/org/kohsuke/github/{OrgInstallationCredentialProviderTest.java => OrgInstallationAuthorizationProviderTest.java} (56%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json (100%) rename src/test/resources/org/kohsuke/github/{OrgInstallationCredentialProviderTest => OrgInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json (100%) diff --git a/src/main/java/org/kohsuke/github/CredentialProvider.java b/src/main/java/org/kohsuke/github/AuthorizationProvider.java similarity index 71% rename from src/main/java/org/kohsuke/github/CredentialProvider.java rename to src/main/java/org/kohsuke/github/AuthorizationProvider.java index ea29f3e9b1..8a5eb6c1a3 100644 --- a/src/main/java/org/kohsuke/github/CredentialProvider.java +++ b/src/main/java/org/kohsuke/github/AuthorizationProvider.java @@ -6,15 +6,15 @@ * Provides a functional interface that returns a valid encodedAuthorization. This strategy allows for a provider that * dynamically changes the credentials. Each request will request the credentials from the provider. */ -public interface CredentialProvider { +public interface AuthorizationProvider { /** - * An static instance for an ANONYMOUS credential provider + * An static instance for an ANONYMOUS authorization provider */ - CredentialProvider ANONYMOUS = new AnonymousCredentialProvider(); + AuthorizationProvider ANONYMOUS = new AnonymousAuthorizationProvider(); /** - * Returns the credentials to be used with a given request. As an example, a credential provider for a bearer token - * will return something like: + * Returns the credentials to be used with a given request. As an example, a authorization provider for a bearer + * token will return something like: * *
      * {@code
@@ -32,7 +32,7 @@ public interface CredentialProvider {
     String getEncodedAuthorization() throws IOException;
 
     /**
-     * Binds this credential provider to a github instance.
+     * Binds this authorization provider to a github instance.
      *
      * Only needs to be implemented by dynamic credentials providers that use a github instance in order to refresh.
      *
@@ -43,9 +43,9 @@ default void bind(GitHub github) {
     }
 
     /**
-     * A {@link CredentialProvider} that ensures that no credentials are returned
+     * A {@link AuthorizationProvider} that ensures that no credentials are returned
      */
-    class AnonymousCredentialProvider implements CredentialProvider {
+    class AnonymousAuthorizationProvider implements AuthorizationProvider {
         @Override
         public String getEncodedAuthorization() throws IOException {
             return null;
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index 05b3d32f83..7a40011824 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -101,23 +101,23 @@ public class GitHub {
      *            abuseLimitHandler
      * @param rateLimitChecker
      *            rateLimitChecker
-     * @param credentialProvider
-     *            a credential provider
+     * @param authorizationProvider
+     *            a authorization provider
      */
     GitHub(String apiUrl,
             HttpConnector connector,
             RateLimitHandler rateLimitHandler,
             AbuseLimitHandler abuseLimitHandler,
             GitHubRateLimitChecker rateLimitChecker,
-            CredentialProvider credentialProvider) throws IOException {
-        credentialProvider.bind(this);
+            AuthorizationProvider authorizationProvider) throws IOException {
+        authorizationProvider.bind(this);
         this.client = new GitHubHttpUrlConnectionClient(apiUrl,
                 connector,
                 rateLimitHandler,
                 abuseLimitHandler,
                 rateLimitChecker,
                 (myself) -> setMyself(myself),
-                credentialProvider);
+                authorizationProvider);
         users = new ConcurrentHashMap<>();
         orgs = new ConcurrentHashMap<>();
     }
@@ -130,12 +130,12 @@ private GitHub(GitHubClient client) {
 
     static class CredentialRefreshGitHubWrapper extends GitHub {
 
-        private final CredentialProvider credentialProvider;
+        private final AuthorizationProvider authorizationProvider;
 
-        CredentialRefreshGitHubWrapper(GitHub github, CredentialProvider credentialProvider) {
+        CredentialRefreshGitHubWrapper(GitHub github, AuthorizationProvider authorizationProvider) {
             super(github.client);
-            this.credentialProvider = credentialProvider;
-            this.credentialProvider.bind(this);
+            this.authorizationProvider = authorizationProvider;
+            this.authorizationProvider.bind(this);
         }
 
         @Nonnull
@@ -143,7 +143,7 @@ static class CredentialRefreshGitHubWrapper extends GitHub {
         Requester createRequest() {
             try {
                 // Override
-                return super.createRequest().setHeader("Authorization", credentialProvider.getEncodedAuthorization())
+                return super.createRequest().setHeader("Authorization", authorizationProvider.getEncodedAuthorization())
                         .rateLimit(RateLimitTarget.NONE);
             } catch (IOException e) {
                 throw new GHException("Failed to create requester to refresh credentials", e);
diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java
index 72c5984ea0..425285f643 100644
--- a/src/main/java/org/kohsuke/github/GitHubBuilder.java
+++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java
@@ -30,7 +30,7 @@ public class GitHubBuilder implements Cloneable {
     private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
     private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT;
     private GitHubRateLimitChecker rateLimitChecker = new GitHubRateLimitChecker();
-    /* private */ CredentialProvider credentialProvider = CredentialProvider.ANONYMOUS;
+    /* private */ AuthorizationProvider authorizationProvider = AuthorizationProvider.ANONYMOUS;
 
     /**
      * Instantiates a new Git hub builder.
@@ -58,13 +58,13 @@ static GitHubBuilder fromCredentials() throws IOException {
 
         builder = fromEnvironment();
 
-        if (builder.credentialProvider != null)
+        if (builder.authorizationProvider != null)
             return builder;
 
         try {
             builder = fromPropertyFile();
 
-            if (builder.credentialProvider != null)
+            if (builder.authorizationProvider != null)
                 return builder;
         } catch (FileNotFoundException e) {
             // fall through
@@ -255,7 +255,7 @@ public GitHubBuilder withEndpoint(String endpoint) {
      * @return the git hub builder
      */
     public GitHubBuilder withPassword(String user, String password) {
-        return withCredentialProvider(ImmutableCredentialProvider.fromLoginAndPassword(user, password));
+        return withAuthorizationProvider(ImmutableAuthorizationProvider.fromLoginAndPassword(user, password));
     }
 
     /**
@@ -266,7 +266,7 @@ public GitHubBuilder withPassword(String user, String password) {
      * @return the git hub builder
      */
     public GitHubBuilder withOAuthToken(String oauthToken) {
-        return withCredentialProvider(ImmutableCredentialProvider.fromOauthToken(oauthToken));
+        return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken));
     }
 
     /**
@@ -279,21 +279,21 @@ public GitHubBuilder withOAuthToken(String oauthToken) {
      * @return the git hub builder
      */
     public GitHubBuilder withOAuthToken(String oauthToken, String user) {
-        return withCredentialProvider(ImmutableCredentialProvider.fromOauthToken(oauthToken, user));
+        return withAuthorizationProvider(ImmutableAuthorizationProvider.fromOauthToken(oauthToken, user));
     }
 
     /**
-     * Configures a {@link CredentialProvider} for this builder
+     * Configures a {@link AuthorizationProvider} for this builder
      *
-     * There can be only one credential provider per client instance.
+     * There can be only one authorization provider per client instance.
      *
-     * @param credentialProvider
-     *            the credential provider
+     * @param authorizationProvider
+     *            the authorization provider
      * @return the git hub builder
      *
      */
-    public GitHubBuilder withCredentialProvider(final CredentialProvider credentialProvider) {
-        this.credentialProvider = credentialProvider;
+    public GitHubBuilder withAuthorizationProvider(final AuthorizationProvider authorizationProvider) {
+        this.authorizationProvider = authorizationProvider;
         return this;
     }
 
@@ -306,7 +306,7 @@ public GitHubBuilder withCredentialProvider(final CredentialProvider credentialP
      * @see GHAppInstallation#createToken(java.util.Map) GHAppInstallation#createToken(java.util.Map)
      */
     public GitHubBuilder withAppInstallationToken(String appInstallationToken) {
-        return withCredentialProvider(ImmutableCredentialProvider.fromAppInstallationToken(appInstallationToken));
+        return withAuthorizationProvider(ImmutableAuthorizationProvider.fromAppInstallationToken(appInstallationToken));
     }
 
     /**
@@ -317,7 +317,7 @@ public GitHubBuilder withAppInstallationToken(String appInstallationToken) {
      * @return the git hub builder
      */
     public GitHubBuilder withJwtToken(String jwtToken) {
-        return withCredentialProvider(ImmutableCredentialProvider.fromJwtToken(jwtToken));
+        return withAuthorizationProvider(ImmutableAuthorizationProvider.fromJwtToken(jwtToken));
     }
 
     /**
@@ -443,7 +443,7 @@ public GitHub build() throws IOException {
                 rateLimitHandler,
                 abuseLimitHandler,
                 rateLimitChecker,
-                credentialProvider);
+                authorizationProvider);
     }
 
     @Override
diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java
index faff6f812e..b7a82db9a6 100644
--- a/src/main/java/org/kohsuke/github/GitHubClient.java
+++ b/src/main/java/org/kohsuke/github/GitHubClient.java
@@ -3,6 +3,7 @@
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
 import org.apache.commons.io.IOUtils;
+import org.kohsuke.github.ImmutableAuthorizationProvider.UserAuthorizationProvider;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -46,7 +47,7 @@ abstract class GitHubClient {
     protected final RateLimitHandler rateLimitHandler;
     protected final AbuseLimitHandler abuseLimitHandler;
     private final GitHubRateLimitChecker rateLimitChecker;
-    private final CredentialProvider credentialProvider;
+    private final AuthorizationProvider authorizationProvider;
 
     private HttpConnector connector;
 
@@ -76,7 +77,7 @@ abstract class GitHubClient {
             AbuseLimitHandler abuseLimitHandler,
             GitHubRateLimitChecker rateLimitChecker,
             Consumer myselfConsumer,
-            CredentialProvider credentialProvider) throws IOException {
+            AuthorizationProvider authorizationProvider) throws IOException {
 
         if (apiUrl.endsWith("/")) {
             apiUrl = apiUrl.substring(0, apiUrl.length() - 1); // normalize
@@ -89,7 +90,7 @@ abstract class GitHubClient {
         this.connector = connector;
 
         // Prefer credential configuration via provider
-        this.credentialProvider = credentialProvider;
+        this.authorizationProvider = authorizationProvider;
 
         this.rateLimitHandler = rateLimitHandler;
         this.abuseLimitHandler = abuseLimitHandler;
@@ -100,12 +101,12 @@ abstract class GitHubClient {
 
     private String getCurrentUser(Consumer myselfConsumer) throws IOException {
         String login = null;
-        if (this.credentialProvider instanceof ImmutableCredentialProvider.UserCredentialProvider
-                && this.credentialProvider.getEncodedAuthorization() != null) {
+        if (this.authorizationProvider instanceof UserAuthorizationProvider
+                && this.authorizationProvider.getEncodedAuthorization() != null) {
 
-            ImmutableCredentialProvider.UserCredentialProvider userCredentialProvider = (ImmutableCredentialProvider.UserCredentialProvider) this.credentialProvider;
+            UserAuthorizationProvider userAuthorizationProvider = (UserAuthorizationProvider) this.authorizationProvider;
 
-            login = userCredentialProvider.getLogin();
+            login = userAuthorizationProvider.getLogin();
 
             if (login == null) {
                 try {
@@ -185,7 +186,7 @@ public void setConnector(HttpConnector connector) {
      */
     public boolean isAnonymous() {
         try {
-            return login == null && this.credentialProvider.getEncodedAuthorization() == null;
+            return login == null && this.authorizationProvider.getEncodedAuthorization() == null;
         } catch (IOException e) {
             // An exception here means that the provider failed to provide authorization parameters,
             // basically meaning the same as "no auth"
@@ -214,7 +215,7 @@ public GHRateLimit getRateLimit() throws IOException {
 
     @CheckForNull
     protected String getEncodedAuthorization() throws IOException {
-        return credentialProvider.getEncodedAuthorization();
+        return authorizationProvider.getEncodedAuthorization();
     }
 
     @Nonnull
diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
index ed130990ec..0b24b8ddda 100644
--- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
+++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java
@@ -38,14 +38,14 @@ class GitHubHttpUrlConnectionClient extends GitHubClient {
             AbuseLimitHandler abuseLimitHandler,
             GitHubRateLimitChecker rateLimitChecker,
             Consumer myselfConsumer,
-            CredentialProvider credentialProvider) throws IOException {
+            AuthorizationProvider authorizationProvider) throws IOException {
         super(apiUrl,
                 connector,
                 rateLimitHandler,
                 abuseLimitHandler,
                 rateLimitChecker,
                 myselfConsumer,
-                credentialProvider);
+                authorizationProvider);
     }
 
     @Nonnull
diff --git a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java b/src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java
similarity index 53%
rename from src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java
rename to src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java
index d7f5815f97..d85241c2be 100644
--- a/src/main/java/org/kohsuke/github/ImmutableCredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java
@@ -7,84 +7,84 @@
 import javax.annotation.CheckForNull;
 
 /**
- * A {@link CredentialProvider} that always returns the same credentials
+ * A {@link AuthorizationProvider} that always returns the same credentials
  */
-public class ImmutableCredentialProvider implements CredentialProvider {
+public class ImmutableAuthorizationProvider implements AuthorizationProvider {
 
     private final String authorization;
 
-    public ImmutableCredentialProvider(String authorization) {
+    public ImmutableAuthorizationProvider(String authorization) {
         this.authorization = authorization;
     }
 
     /**
-     * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken
+     * Builds and returns a {@link AuthorizationProvider} from a given oauthAccessToken
      *
      * @param oauthAccessToken
      *            The token
-     * @return a correctly configured {@link CredentialProvider} that will always return the same provided
+     * @return a correctly configured {@link AuthorizationProvider} that will always return the same provided
      *         oauthAccessToken
      */
-    public static CredentialProvider fromOauthToken(String oauthAccessToken) {
-        return new UserCredentialProvider(String.format("token %s", oauthAccessToken));
+    public static AuthorizationProvider fromOauthToken(String oauthAccessToken) {
+        return new UserAuthorizationProvider(String.format("token %s", oauthAccessToken));
     }
 
     /**
-     * Builds and returns a {@link CredentialProvider} from a given oauthAccessToken
+     * Builds and returns a {@link AuthorizationProvider} from a given oauthAccessToken
      *
      * @param oauthAccessToken
      *            The token
      * @param login
      *            The login for this token
      *
-     * @return a correctly configured {@link CredentialProvider} that will always return the same provided
+     * @return a correctly configured {@link AuthorizationProvider} that will always return the same provided
      *         oauthAccessToken
      */
-    public static CredentialProvider fromOauthToken(String oauthAccessToken, String login) {
-        return new UserCredentialProvider(String.format("token %s", oauthAccessToken), login);
+    public static AuthorizationProvider fromOauthToken(String oauthAccessToken, String login) {
+        return new UserAuthorizationProvider(String.format("token %s", oauthAccessToken), login);
     }
 
     /**
-     * Builds and returns a {@link CredentialProvider} from a given App Installation Token
+     * Builds and returns a {@link AuthorizationProvider} from a given App Installation Token
      *
      * @param appInstallationToken
      *            A string containing the GitHub App installation token
      * @return the configured Builder from given GitHub App installation token.
      */
-    public static CredentialProvider fromAppInstallationToken(String appInstallationToken) {
+    public static AuthorizationProvider fromAppInstallationToken(String appInstallationToken) {
         return fromOauthToken(appInstallationToken, "");
     }
 
     /**
-     * Builds and returns a {@link CredentialProvider} from a given jwtToken
+     * Builds and returns a {@link AuthorizationProvider} from a given jwtToken
      *
      * @param jwtToken
      *            The JWT token
-     * @return a correctly configured {@link CredentialProvider} that will always return the same provided jwtToken
+     * @return a correctly configured {@link AuthorizationProvider} that will always return the same provided jwtToken
      */
-    public static CredentialProvider fromJwtToken(String jwtToken) {
-        return new ImmutableCredentialProvider(String.format("Bearer %s", jwtToken));
+    public static AuthorizationProvider fromJwtToken(String jwtToken) {
+        return new ImmutableAuthorizationProvider(String.format("Bearer %s", jwtToken));
     }
 
     /**
-     * Builds and returns a {@link CredentialProvider} from the given user/password pair
+     * Builds and returns a {@link AuthorizationProvider} from the given user/password pair
      *
      * @param login
      *            The login for the user, usually the same as the username
      * @param password
      *            The password for the associated user
-     * @return a correctly configured {@link CredentialProvider} that will always return the credentials for the same
+     * @return a correctly configured {@link AuthorizationProvider} that will always return the credentials for the same
      *         user and password combo
      * @deprecated Login with password credentials are no longer supported by GitHub
      */
     @Deprecated
-    public static CredentialProvider fromLoginAndPassword(String login, String password) {
+    public static AuthorizationProvider fromLoginAndPassword(String login, String password) {
         try {
             String authorization = (String.format("%s:%s", login, password));
             String charsetName = StandardCharsets.UTF_8.name();
             String b64encoded = Base64.getEncoder().encodeToString(authorization.getBytes(charsetName));
             String encodedAuthorization = String.format("Basic %s", b64encoded);
-            return new UserCredentialProvider(encodedAuthorization, login);
+            return new UserAuthorizationProvider(encodedAuthorization, login);
         } catch (UnsupportedEncodingException e) {
             // If UTF-8 isn't supported, there are bigger problems
             throw new IllegalStateException("Could not generate encoded authorization", e);
@@ -100,15 +100,15 @@ public String getEncodedAuthorization() {
      * An internal class representing all user-related credentials, which are credentials that have a login or should
      * query the user endpoint for the login matching this credential.
      */
-    static class UserCredentialProvider extends ImmutableCredentialProvider {
+    static class UserAuthorizationProvider extends ImmutableAuthorizationProvider {
 
         private final String login;
 
-        UserCredentialProvider(String authorization) {
+        UserAuthorizationProvider(String authorization) {
             this(authorization, null);
         }
 
-        UserCredentialProvider(String authorization, String login) {
+        UserAuthorizationProvider(String authorization, String login) {
             super(authorization);
             this.login = login;
         }
diff --git a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java b/src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java
similarity index 69%
rename from src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java
rename to src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java
index f4a3bc7c11..8715ac19ce 100644
--- a/src/main/java/org/kohsuke/github/OrgInstallationCredentialProvider.java
+++ b/src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java
@@ -8,14 +8,14 @@
 import javax.annotation.Nonnull;
 
 /**
- * Provides a CredentialProvider that performs automatic token refresh.
+ * Provides an AuthorizationProvider that performs automatic token refresh.
  */
-public class OrgInstallationCredentialProvider implements CredentialProvider {
+public class OrgInstallationAuthorizationProvider implements AuthorizationProvider {
 
     private GitHub baseGitHub;
     private GitHub gitHub;
 
-    private final CredentialProvider refreshProvider;
+    private final AuthorizationProvider refreshProvider;
     private final String organizationName;
 
     private String latestToken;
@@ -24,20 +24,20 @@ public class OrgInstallationCredentialProvider implements CredentialProvider {
     private Instant validUntil = Instant.MIN;
 
     /**
-     * Provides a CredentialProvider that performs automatic token refresh, based on an previously authenticated github
-     * client.
+     * Provides an AuthorizationProvider that performs automatic token refresh, based on an previously authenticated
+     * github client.
      *
      * @param organizationName
      *            The name of the organization where the application is installed
-     * @param credentialProvider
-     *            A credential provider that returns a JWT token that can be used to refresh the App Installation token
-     *            from GitHub.
+     * @param authorizationProvider
+     *            A authorization provider that returns a JWT token that can be used to refresh the App Installation
+     *            token from GitHub.
      */
     @BetaApi
     @Deprecated
-    public OrgInstallationCredentialProvider(String organizationName, CredentialProvider credentialProvider) {
+    public OrgInstallationAuthorizationProvider(String organizationName, AuthorizationProvider authorizationProvider) {
         this.organizationName = organizationName;
-        this.refreshProvider = credentialProvider;
+        this.refreshProvider = authorizationProvider;
     }
 
     @Override
diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
index 28e0291969..1e06fa72f1 100644
--- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
@@ -3,7 +3,7 @@
 import io.jsonwebtoken.JwtBuilder;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
-import org.kohsuke.github.CredentialProvider;
+import org.kohsuke.github.AuthorizationProvider;
 
 import java.io.File;
 import java.io.IOException;
@@ -23,11 +23,11 @@
 import javax.annotation.Nonnull;
 
 /**
- * A credential provider that gives valid JWT tokens. These tokens are then used to create a time-based token to
+ * A authorization provider that gives valid JWT tokens. These tokens are then used to create a time-based token to
  * authenticate as an application. This token provider does not provide any kind of caching, and will always request a
  * new token to the API.
  */
-public class JWTTokenProvider implements CredentialProvider {
+public class JWTTokenProvider implements AuthorizationProvider {
 
     private static final long MINUTES_10 = Duration.ofMinutes(10).toMillis();
 
diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
index ea989d65a8..dca158a634 100644
--- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
+++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java
@@ -24,9 +24,9 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
     private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem";
     private static String PRIVATE_KEY_FILE_APP_3 = "/ghapi-test-app-3.private-key.pem";
 
-    private static CredentialProvider JWT_PROVIDER_1;
-    private static CredentialProvider JWT_PROVIDER_2;
-    private static CredentialProvider JWT_PROVIDER_3;
+    private static AuthorizationProvider JWT_PROVIDER_1;
+    private static AuthorizationProvider JWT_PROVIDER_2;
+    private static AuthorizationProvider JWT_PROVIDER_3;
 
     AbstractGHAppInstallationTest() {
         try {
diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
index 9f5a9a3e51..2cb5808b9c 100644
--- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
+++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java
@@ -1,6 +1,7 @@
 package org.kohsuke.github;
 
 import org.junit.Test;
+import org.kohsuke.github.ImmutableAuthorizationProvider.UserAuthorizationProvider;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
@@ -65,37 +66,33 @@ public void testGitHubBuilderFromEnvironment() throws IOException {
 
         assertThat(builder.endpoint, equalTo("bogus endpoint url"));
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                nullValue());
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), nullValue());
 
         props.put("login", "bogus login");
         setupEnvironment(props);
         builder = GitHubBuilder.fromEnvironment();
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                equalTo("bogus login"));
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login"));
 
         props.put("jwt", "bogus jwt token string");
         setupEnvironment(props);
         builder = GitHubBuilder.fromEnvironment();
 
-        assertThat(builder.credentialProvider,
-                not(instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class)));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("Bearer bogus jwt token string"));
+        assertThat(builder.authorizationProvider, not(instanceOf(UserAuthorizationProvider.class)));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("Bearer bogus jwt token string"));
 
         props.put("password", "bogus weak password");
         setupEnvironment(props);
         builder = GitHubBuilder.fromEnvironment();
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(),
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(),
                 equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA=="));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                equalTo("bogus login"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login"));
 
     }
 
@@ -111,39 +108,35 @@ public void testGitHubBuilderFromCustomEnvironment() throws IOException {
 
         assertThat(builder.endpoint, equalTo("bogus endpoint url"));
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                nullValue());
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), nullValue());
 
         props.put("customLogin", "bogus login");
         setupEnvironment(props);
         builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint");
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                equalTo("bogus login"));
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus oauth token string"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login"));
 
         props.put("customPassword", "bogus weak password");
         setupEnvironment(props);
         builder = GitHubBuilder.fromEnvironment("customLogin", "customPassword", "customOauth", "customEndpoint");
 
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(),
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(),
                 equalTo("Basic Ym9ndXMgbG9naW46Ym9ndXMgd2VhayBwYXNzd29yZA=="));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                equalTo("bogus login"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo("bogus login"));
     }
 
     @Test
     public void testGithubBuilderWithAppInstallationToken() throws Exception {
 
         GitHubBuilder builder = new GitHubBuilder().withAppInstallationToken("bogus app token");
-        assertThat(builder.credentialProvider, instanceOf(ImmutableCredentialProvider.UserCredentialProvider.class));
-        assertThat(builder.credentialProvider.getEncodedAuthorization(), equalTo("token bogus app token"));
-        assertThat(((ImmutableCredentialProvider.UserCredentialProvider) builder.credentialProvider).getLogin(),
-                equalTo(""));
+        assertThat(builder.authorizationProvider, instanceOf(UserAuthorizationProvider.class));
+        assertThat(builder.authorizationProvider.getEncodedAuthorization(), equalTo("token bogus app token"));
+        assertThat(((UserAuthorizationProvider) builder.authorizationProvider).getLogin(), equalTo(""));
 
         // test authorization header is set as in the RFC6749
         GitHub github = builder.build();
diff --git a/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java b/src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java
similarity index 56%
rename from src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java
rename to src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java
index 032aed44aa..c99b8a5819 100644
--- a/src/test/java/org/kohsuke/github/OrgInstallationCredentialProviderTest.java
+++ b/src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java
@@ -7,17 +7,17 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 
-public class OrgInstallationCredentialProviderTest extends AbstractGHAppInstallationTest {
+public class OrgInstallationAuthorizationProviderTest extends AbstractGHAppInstallationTest {
 
-    public OrgInstallationCredentialProviderTest() {
+    public OrgInstallationAuthorizationProviderTest() {
         useDefaultGitHub = false;
     }
 
     @Test(expected = HttpException.class)
     public void invalidJWTTokenRaisesException() throws IOException {
-        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("testOrganization",
-                ImmutableCredentialProvider.fromJwtToken("myToken"));
-        gitHub = getGitHubBuilder().withCredentialProvider(provider)
+        OrgInstallationAuthorizationProvider provider = new OrgInstallationAuthorizationProvider("testOrganization",
+                ImmutableAuthorizationProvider.fromJwtToken("myToken"));
+        gitHub = getGitHubBuilder().withAuthorizationProvider(provider)
                 .withEndpoint(mockGitHub.apiServer().baseUrl())
                 .build();
 
@@ -26,9 +26,9 @@ public void invalidJWTTokenRaisesException() throws IOException {
 
     @Test
     public void validJWTTokenAllowsOauthTokenRequest() throws IOException {
-        OrgInstallationCredentialProvider provider = new OrgInstallationCredentialProvider("hub4j-test-org",
-                ImmutableCredentialProvider.fromJwtToken("bogus-valid-token"));
-        gitHub = getGitHubBuilder().withCredentialProvider(provider)
+        OrgInstallationAuthorizationProvider provider = new OrgInstallationAuthorizationProvider("hub4j-test-org",
+                ImmutableAuthorizationProvider.fromJwtToken("bogus-valid-token"));
+        gitHub = getGitHubBuilder().withAuthorizationProvider(provider)
                 .withEndpoint(mockGitHub.apiServer().baseUrl())
                 .build();
         String encodedAuthorization = provider.getEncodedAuthorization();
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationCredentialProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json
rename to src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json

From cdede298a94dbc48a07d4eb0bbc3d4380fd53b5c Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Thu, 7 Jan 2021 09:53:19 +0100
Subject: [PATCH 36/44] rename OrgInstallationAuthorizationProvider to
 OrgAppInstallationAuthorizationProvider

---
 ...java => OrgAppInstallationAuthorizationProvider.java} | 5 +++--
 ... => OrgAppInstallationAuthorizationProviderTest.java} | 9 +++++----
 .../invalidJWTTokenRaisesException/mappings/app-2.json   | 0
 .../invalidJWTTokenRaisesException/mappings/user-1.json  | 0
 .../__files/app-2.json                                   | 0
 .../__files/orgs_hub4j-test-org_installation-3.json      | 0
 .../mappings/app-2.json                                  | 0
 .../app_installations_11575015_access_tokens-4.json      | 0
 .../mappings/orgs_hub4j-test-org_installation-3.json     | 0
 .../mappings/user-1.json                                 | 0
 10 files changed, 8 insertions(+), 6 deletions(-)
 rename src/main/java/org/kohsuke/github/{OrgInstallationAuthorizationProvider.java => OrgAppInstallationAuthorizationProvider.java} (90%)
 rename src/test/java/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest.java => OrgAppInstallationAuthorizationProviderTest.java} (74%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json (100%)
 rename src/test/resources/org/kohsuke/github/{OrgInstallationAuthorizationProviderTest => OrgAppInstallationAuthorizationProviderTest}/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json (100%)

diff --git a/src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java b/src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java
similarity index 90%
rename from src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java
rename to src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java
index 8715ac19ce..84134f83b1 100644
--- a/src/main/java/org/kohsuke/github/OrgInstallationAuthorizationProvider.java
+++ b/src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java
@@ -10,7 +10,7 @@
 /**
  * Provides an AuthorizationProvider that performs automatic token refresh.
  */
-public class OrgInstallationAuthorizationProvider implements AuthorizationProvider {
+public class OrgAppInstallationAuthorizationProvider implements AuthorizationProvider {
 
     private GitHub baseGitHub;
     private GitHub gitHub;
@@ -35,7 +35,8 @@ public class OrgInstallationAuthorizationProvider implements AuthorizationProvid
      */
     @BetaApi
     @Deprecated
-    public OrgInstallationAuthorizationProvider(String organizationName, AuthorizationProvider authorizationProvider) {
+    public OrgAppInstallationAuthorizationProvider(String organizationName,
+            AuthorizationProvider authorizationProvider) {
         this.organizationName = organizationName;
         this.refreshProvider = authorizationProvider;
     }
diff --git a/src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java b/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java
similarity index 74%
rename from src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java
rename to src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java
index c99b8a5819..585d87fe7e 100644
--- a/src/test/java/org/kohsuke/github/OrgInstallationAuthorizationProviderTest.java
+++ b/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java
@@ -7,15 +7,16 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.notNullValue;
 
-public class OrgInstallationAuthorizationProviderTest extends AbstractGHAppInstallationTest {
+public class OrgAppInstallationAuthorizationProviderTest extends AbstractGHAppInstallationTest {
 
-    public OrgInstallationAuthorizationProviderTest() {
+    public OrgAppInstallationAuthorizationProviderTest() {
         useDefaultGitHub = false;
     }
 
     @Test(expected = HttpException.class)
     public void invalidJWTTokenRaisesException() throws IOException {
-        OrgInstallationAuthorizationProvider provider = new OrgInstallationAuthorizationProvider("testOrganization",
+        OrgAppInstallationAuthorizationProvider provider = new OrgAppInstallationAuthorizationProvider(
+                "testOrganization",
                 ImmutableAuthorizationProvider.fromJwtToken("myToken"));
         gitHub = getGitHubBuilder().withAuthorizationProvider(provider)
                 .withEndpoint(mockGitHub.apiServer().baseUrl())
@@ -26,7 +27,7 @@ public void invalidJWTTokenRaisesException() throws IOException {
 
     @Test
     public void validJWTTokenAllowsOauthTokenRequest() throws IOException {
-        OrgInstallationAuthorizationProvider provider = new OrgInstallationAuthorizationProvider("hub4j-test-org",
+        OrgAppInstallationAuthorizationProvider provider = new OrgAppInstallationAuthorizationProvider("hub4j-test-org",
                 ImmutableAuthorizationProvider.fromJwtToken("bogus-valid-token"));
         gitHub = getGitHubBuilder().withAuthorizationProvider(provider)
                 .withEndpoint(mockGitHub.apiServer().baseUrl())
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/invalidJWTTokenRaisesException/mappings/user-1.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/__files/orgs_hub4j-test-org_installation-3.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app-2.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/app_installations_11575015_access_tokens-4.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/orgs_hub4j-test-org_installation-3.json
diff --git a/src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json b/src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json
similarity index 100%
rename from src/test/resources/org/kohsuke/github/OrgInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json
rename to src/test/resources/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest/wiremock/validJWTTokenAllowsOauthTokenRequest/mappings/user-1.json

From 44a8b797fbae552a211a042235a7a97275f1c5e4 Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Thu, 7 Jan 2021 11:23:22 +0100
Subject: [PATCH 37/44] fix: JWTTokenProvider has an incorrect value for the
 returned authorization header

more info:
https://docs.github.com/en/free-pro-team@latest/developers/apps/authenticating-with-github-apps#authenticating-as-a-github-app
---
 .../java/org/kohsuke/github/extras/auth/JWTTokenProvider.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
index 1e06fa72f1..9d809090b8 100644
--- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
@@ -62,7 +62,7 @@ public String getEncodedAuthorization() throws IOException {
             if (Instant.now().isAfter(validUntil)) {
                 token = refreshJWT();
             }
-            return token;
+            return String.format("Bearer %s", token);
         }
     }
 

From 0e2bf238300441d08d509611e722156a69f9d01f Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Thu, 7 Jan 2021 11:32:33 +0100
Subject: [PATCH 38/44] add CODE_SCANNING_ALERT to GHEvent enum

---
 src/main/java/org/kohsuke/github/GHEvent.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/org/kohsuke/github/GHEvent.java b/src/main/java/org/kohsuke/github/GHEvent.java
index 0fb2834cb8..481dfa27bd 100644
--- a/src/main/java/org/kohsuke/github/GHEvent.java
+++ b/src/main/java/org/kohsuke/github/GHEvent.java
@@ -12,6 +12,7 @@
 public enum GHEvent {
     CHECK_RUN,
     CHECK_SUITE,
+    CODE_SCANNING_ALERT,
     COMMIT_COMMENT,
     CONTENT_REFERENCE,
     CREATE,

From ca7c809feb190f44d211ac660adfe0c0569455a6 Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Fri, 8 Jan 2021 08:21:35 +0100
Subject: [PATCH 39/44] remove unused field MINUTES_10 from JWTTokenProvider

---
 .../java/org/kohsuke/github/extras/auth/JWTTokenProvider.java   | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
index 9d809090b8..64e0aedd8c 100644
--- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
+++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java
@@ -29,8 +29,6 @@
  */
 public class JWTTokenProvider implements AuthorizationProvider {
 
-    private static final long MINUTES_10 = Duration.ofMinutes(10).toMillis();
-
     private final PrivateKey privateKey;
 
     @Nonnull

From a96275c286ab82b1ccf8931bc0b985d318b8cf5b Mon Sep 17 00:00:00 2001
From: "Marcos.Cela" 
Date: Fri, 8 Jan 2021 09:52:50 +0100
Subject: [PATCH 40/44] tests for JWTTokenProvider, verifying the
 "Authentication" header

This test basically ensures that the requests made with a
JWTTokenProvider follow a valid Authentication pattern,
verifying that the header "conforms" to a valid JWT token
More information on JWT tokens can be found at:

- https://jwt.io/introduction/
---
 .../extras/auth/JWTTokenProviderTest.java     | 48 +++++++++++++++++++
 .../__files/app-1.json                        | 34 +++++++++++++
 .../mappings/app-1.json                       | 44 +++++++++++++++++
 3 files changed, 126 insertions(+)
 create mode 100644 src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java
 create mode 100644 src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json
 create mode 100644 src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json

diff --git a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java
new file mode 100644
index 0000000000..83f89f6f95
--- /dev/null
+++ b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java
@@ -0,0 +1,48 @@
+package org.kohsuke.github.extras.auth;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.junit.Test;
+import org.kohsuke.github.AbstractGitHubWireMockTest;
+import org.kohsuke.github.GitHub;
+
+public class JWTTokenProviderTest extends AbstractGitHubWireMockTest {
+
+    private static String TEST_APP_ID_2 = "83009";
+    private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem";
+
+    /**
+     * This test will request an application ensuring that the header for the "Authorization" matches a valid JWT token.
+     * A JWT token in the Authorization header will always start with "ey" which is always the start of the base64
+     * encoding of the JWT Header , so a valid header will look like this:
+     *
+     * 
+     * Authorization: Bearer ey{rest of the header}.{payload}.{signature}
+     * 
+ * + * Matched by the regular expression: + * + *
+     * ^Bearer (?ey\S*)\.(?\S*)\.(?\S*)$
+     * 
+ * + * Which is present in the wiremock matcher. Note that we need to use a matcher because the JWT token is encoded + * with a private key and a random nonce, so it will never be the same (under normal conditions). For more + * information on the format of a JWT token, see: https://jwt.io/introduction/ + */ + @Test + public void testAuthorizationHeaderPattern() throws GeneralSecurityException, IOException { + JWTTokenProvider jwtTokenProvider = new JWTTokenProvider(TEST_APP_ID_2, + new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile())); + GitHub gh = getGitHubBuilder().withEndpoint(mockGitHub.apiServer().baseUrl()) + .withAuthorizationProvider(jwtTokenProvider) + .build(); + + // Request the application, the wiremock matcher will ensure that the header + // for the authorization is present and has a the format of a valid JWT token + gh.getApp(); + } + +} diff --git a/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json b/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json new file mode 100644 index 0000000000..8cc884b88a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json @@ -0,0 +1,34 @@ +{ + "id": 83009, + "slug": "ghapi-test-app-2", + "node_id": "MDM6QXBwODMwMDk=", + "owner": { + "login": "hub4j-test-org", + "id": 7544739, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=", + "avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/hub4j-test-org", + "html_url": "https://github.com/hub4j-test-org", + "followers_url": "https://api.github.com/users/hub4j-test-org/followers", + "following_url": "https://api.github.com/users/hub4j-test-org/following{/other_user}", + "gists_url": "https://api.github.com/users/hub4j-test-org/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hub4j-test-org/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hub4j-test-org/subscriptions", + "organizations_url": "https://api.github.com/users/hub4j-test-org/orgs", + "repos_url": "https://api.github.com/users/hub4j-test-org/repos", + "events_url": "https://api.github.com/users/hub4j-test-org/events{/privacy}", + "received_events_url": "https://api.github.com/users/hub4j-test-org/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "GHApi Test app 2", + "description": "", + "external_url": "https://localhost", + "html_url": "https://github.com/apps/ghapi-test-app-2", + "created_at": "2020-09-30T15:02:20Z", + "updated_at": "2020-09-30T15:02:20Z", + "permissions": {}, + "events": [], + "installations_count": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json b/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json new file mode 100644 index 0000000000..f74c924f95 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json @@ -0,0 +1,44 @@ +{ + "id": "bb7cf5bb-45b3-fba2-afd8-939b2c24787a", + "name": "app", + "request": { + "url": "/app", + "method": "GET", + "headers": { + "Authorization": { + "matches": "^Bearer (?ey\\S*)\\.(?\\S*)\\.(?\\S*)$" + }, + "Accept": { + "equalTo": "application/vnd.github.machine-man-preview+json" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "app-1.json", + "headers": { + "Date": "Thu, 05 Nov 2020 20:42:31 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": [ + "Accept", + "Accept-Encoding, Accept, X-Requested-With", + "Accept-Encoding" + ], + "ETag": "W/\"b3d319dbb4dba93fbda071208d874e5ab566d827e1ad1d7dc59f26d68694dc48\"", + "X-GitHub-Media-Type": "github.v3; param=machine-man-preview; format=json", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "9294:AE05:BDAC761:DB35838:5FA463B6" + } + }, + "uuid": "bb7cf5bb-45b3-fba2-afd8-939b2c24787a", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file From e0a709676e011081257ee0151a857cf27aa07331 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Fri, 8 Jan 2021 09:56:05 +0100 Subject: [PATCH 41/44] fix format violations --- .../kohsuke/github/extras/auth/JWTTokenProviderTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java index 83f89f6f95..2ad7370126 100644 --- a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java +++ b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java @@ -1,13 +1,13 @@ package org.kohsuke.github.extras.auth; -import java.io.File; -import java.io.IOException; -import java.security.GeneralSecurityException; - import org.junit.Test; import org.kohsuke.github.AbstractGitHubWireMockTest; import org.kohsuke.github.GitHub; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; + public class JWTTokenProviderTest extends AbstractGitHubWireMockTest { private static String TEST_APP_ID_2 = "83009"; From 747c759bbbf5b4513df3833990a5c2531ad05891 Mon Sep 17 00:00:00 2001 From: "Marcos.Cela" Date: Fri, 8 Jan 2021 10:10:44 +0100 Subject: [PATCH 42/44] fix code violations again --- .../extras/auth/JWTTokenProviderTest.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java index 2ad7370126..7cca641c42 100644 --- a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java +++ b/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java @@ -7,31 +7,30 @@ import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; - +/* + * This test will request an application ensuring that the header for the "Authorization" matches a valid JWT token. + * A JWT token in the Authorization header will always start with "ey" which is always the start of the base64 + * encoding of the JWT Header , so a valid header will look like this: + * + *
+ * Authorization: Bearer ey{rest of the header}.{payload}.{signature}
+ * 
+ * + * Matched by the regular expression: + * + *
+ * ^Bearer (?ey\S*)\.(?\S*)\.(?\S*)$
+ * 
+ * + * Which is present in the wiremock matcher. Note that we need to use a matcher because the JWT token is encoded + * with a private key and a random nonce, so it will never be the same (under normal conditions). For more + * information on the format of a JWT token, see: https://jwt.io/introduction/ + */ public class JWTTokenProviderTest extends AbstractGitHubWireMockTest { private static String TEST_APP_ID_2 = "83009"; private static String PRIVATE_KEY_FILE_APP_2 = "/ghapi-test-app-2.private-key.pem"; - /** - * This test will request an application ensuring that the header for the "Authorization" matches a valid JWT token. - * A JWT token in the Authorization header will always start with "ey" which is always the start of the base64 - * encoding of the JWT Header , so a valid header will look like this: - * - *
-     * Authorization: Bearer ey{rest of the header}.{payload}.{signature}
-     * 
- * - * Matched by the regular expression: - * - *
-     * ^Bearer (?ey\S*)\.(?\S*)\.(?\S*)$
-     * 
- * - * Which is present in the wiremock matcher. Note that we need to use a matcher because the JWT token is encoded - * with a private key and a random nonce, so it will never be the same (under normal conditions). For more - * information on the format of a JWT token, see: https://jwt.io/introduction/ - */ @Test public void testAuthorizationHeaderPattern() throws GeneralSecurityException, IOException { JWTTokenProvider jwtTokenProvider = new JWTTokenProvider(TEST_APP_ID_2, From c33e78a7dc57bd1a7200708251fc0145831c646f Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 14 Jan 2021 09:23:17 -0800 Subject: [PATCH 43/44] Create authorization package --- src/main/java/org/kohsuke/github/GitHub.java | 5 +++-- src/main/java/org/kohsuke/github/GitHubBuilder.java | 2 ++ src/main/java/org/kohsuke/github/GitHubClient.java | 3 ++- .../org/kohsuke/github/GitHubHttpUrlConnectionClient.java | 1 + .../github/{ => authorization}/AuthorizationProvider.java | 4 +++- .../ImmutableAuthorizationProvider.java | 2 +- .../OrgAppInstallationAuthorizationProvider.java | 8 +++++++- .../org/kohsuke/github/extras/auth/JWTTokenProvider.java | 2 +- .../org/kohsuke/github/AbstractGHAppInstallationTest.java | 1 + .../java/org/kohsuke/github/GitHubConnectionTest.java | 2 +- .../OrgAppInstallationAuthorizationProviderTest.java | 2 ++ 11 files changed, 24 insertions(+), 8 deletions(-) rename src/main/java/org/kohsuke/github/{ => authorization}/AuthorizationProvider.java (95%) rename src/main/java/org/kohsuke/github/{ => authorization}/ImmutableAuthorizationProvider.java (99%) rename src/main/java/org/kohsuke/github/{ => authorization}/OrgAppInstallationAuthorizationProvider.java (89%) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 80b268946e..269f0c5972 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import org.kohsuke.github.authorization.AuthorizationProvider; import org.kohsuke.github.internal.Previews; import java.io.*; @@ -129,11 +130,11 @@ private GitHub(GitHubClient client) { orgs = new ConcurrentHashMap<>(); } - static class CredentialRefreshGitHubWrapper extends GitHub { + private static class AuthorizationRefreshGitHubWrapper extends GitHub { private final AuthorizationProvider authorizationProvider; - CredentialRefreshGitHubWrapper(GitHub github, AuthorizationProvider authorizationProvider) { + AuthorizationRefreshGitHubWrapper(GitHub github, AuthorizationProvider authorizationProvider) { super(github.client); this.authorizationProvider = authorizationProvider; this.authorizationProvider.bind(this); diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 425285f643..94ba46a0e4 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -1,6 +1,8 @@ package org.kohsuke.github; import org.apache.commons.io.IOUtils; +import org.kohsuke.github.authorization.AuthorizationProvider; +import org.kohsuke.github.authorization.ImmutableAuthorizationProvider; import org.kohsuke.github.extras.ImpatientHttpConnector; import java.io.File; diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index b7a82db9a6..83df3c64e4 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -3,7 +3,8 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; import org.apache.commons.io.IOUtils; -import org.kohsuke.github.ImmutableAuthorizationProvider.UserAuthorizationProvider; +import org.kohsuke.github.authorization.ImmutableAuthorizationProvider.UserAuthorizationProvider; +import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java index 0b24b8ddda..0fcfc65769 100644 --- a/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java +++ b/src/main/java/org/kohsuke/github/GitHubHttpUrlConnectionClient.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import org.apache.commons.io.IOUtils; +import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/org/kohsuke/github/AuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java similarity index 95% rename from src/main/java/org/kohsuke/github/AuthorizationProvider.java rename to src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java index 8a5eb6c1a3..e9347c4ca7 100644 --- a/src/main/java/org/kohsuke/github/AuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java @@ -1,4 +1,6 @@ -package org.kohsuke.github; +package org.kohsuke.github.authorization; + +import org.kohsuke.github.GitHub; import java.io.IOException; diff --git a/src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java similarity index 99% rename from src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java rename to src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java index d85241c2be..894564243c 100644 --- a/src/main/java/org/kohsuke/github/ImmutableAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java @@ -1,4 +1,4 @@ -package org.kohsuke.github; +package org.kohsuke.github.authorization; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; diff --git a/src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java similarity index 89% rename from src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java rename to src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java index 84134f83b1..c3772b43eb 100644 --- a/src/main/java/org/kohsuke/github/OrgAppInstallationAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java @@ -1,4 +1,10 @@ -package org.kohsuke.github; +package org.kohsuke.github.authorization; + +import org.kohsuke.github.BetaApi; +import org.kohsuke.github.GHAppInstallation; +import org.kohsuke.github.GHAppInstallationToken; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.IOException; import java.time.Duration; diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java index 64e0aedd8c..74622b2c19 100644 --- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java +++ b/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java @@ -3,7 +3,7 @@ import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import org.kohsuke.github.AuthorizationProvider; +import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.File; import java.io.IOException; diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java index dca158a634..bc0dc92a60 100644 --- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java @@ -2,6 +2,7 @@ import io.jsonwebtoken.Jwts; import org.apache.commons.io.IOUtils; +import org.kohsuke.github.authorization.AuthorizationProvider; import org.kohsuke.github.extras.auth.JWTTokenProvider; import java.io.File; diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java index 2cb5808b9c..481933f95e 100644 --- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java +++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java @@ -1,7 +1,7 @@ package org.kohsuke.github; import org.junit.Test; -import org.kohsuke.github.ImmutableAuthorizationProvider.UserAuthorizationProvider; +import org.kohsuke.github.authorization.ImmutableAuthorizationProvider.UserAuthorizationProvider; import java.io.IOException; import java.lang.reflect.Field; diff --git a/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java b/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java index 585d87fe7e..987e9a64a2 100644 --- a/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java +++ b/src/test/java/org/kohsuke/github/OrgAppInstallationAuthorizationProviderTest.java @@ -1,6 +1,8 @@ package org.kohsuke.github; import org.junit.Test; +import org.kohsuke.github.authorization.ImmutableAuthorizationProvider; +import org.kohsuke.github.authorization.OrgAppInstallationAuthorizationProvider; import java.io.IOException; From 1b84efdbfa510ed3775b78d7f491921b154ecb40 Mon Sep 17 00:00:00 2001 From: Liam Newman Date: Thu, 14 Jan 2021 10:32:25 -0800 Subject: [PATCH 44/44] Add GitHub.DependentAuthorizationProvider Rather than exposing an unsafe wrapper for GitHub instances, I added a base class that can be extended by anyone wanting to implement an authorization provider that needs a GitHub instance to generate it's authorization string. --- src/main/java/org/kohsuke/github/GitHub.java | 52 ++++++++++++++++++- .../java/org/kohsuke/github/GitHubClient.java | 2 +- .../authorization/AuthorizationProvider.java | 13 ----- .../ImmutableAuthorizationProvider.java | 15 +++--- ...gAppInstallationAuthorizationProvider.java | 19 ++----- .../UserAuthorizationProvider.java | 22 ++++++++ .../JWTTokenProvider.java | 2 +- .../github/AbstractGHAppInstallationTest.java | 2 +- .../kohsuke/github/GitHubConnectionTest.java | 2 +- .../JWTTokenProviderTest.java | 2 +- .../__files/app-1.json | 0 .../mappings/app-1.json | 0 12 files changed, 88 insertions(+), 43 deletions(-) create mode 100644 src/main/java/org/kohsuke/github/authorization/UserAuthorizationProvider.java rename src/main/java/org/kohsuke/github/extras/{auth => authorization}/JWTTokenProvider.java (98%) rename src/test/java/org/kohsuke/github/extras/{auth => authorization}/JWTTokenProviderTest.java (97%) rename src/test/resources/org/kohsuke/github/extras/{auth => authorization}/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json (100%) rename src/test/resources/org/kohsuke/github/extras/{auth => authorization}/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json (100%) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 269f0c5972..ef070b4423 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -112,7 +112,10 @@ public class GitHub { AbuseLimitHandler abuseLimitHandler, GitHubRateLimitChecker rateLimitChecker, AuthorizationProvider authorizationProvider) throws IOException { - authorizationProvider.bind(this); + if (authorizationProvider instanceof DependentAuthorizationProvider) { + ((DependentAuthorizationProvider) authorizationProvider).bind(this); + } + this.client = new GitHubHttpUrlConnectionClient(apiUrl, connector, rateLimitHandler, @@ -130,6 +133,47 @@ private GitHub(GitHubClient client) { orgs = new ConcurrentHashMap<>(); } + public static abstract class DependentAuthorizationProvider implements AuthorizationProvider { + + private GitHub baseGitHub; + private GitHub gitHub; + private final AuthorizationProvider authorizationProvider; + + /** + * An AuthorizationProvider that requires an authenticated GitHub instance to provide its authorization. + * + * @param authorizationProvider + * A authorization provider to be used when refreshing this authorization provider. + */ + @BetaApi + @Deprecated + protected DependentAuthorizationProvider(AuthorizationProvider authorizationProvider) { + this.authorizationProvider = authorizationProvider; + } + + /** + * Binds this authorization provider to a github instance. + * + * Only needs to be implemented by dynamic credentials providers that use a github instance in order to refresh. + * + * @param github + * The github instance to be used for refreshing dynamic credentials + */ + synchronized void bind(GitHub github) { + if (baseGitHub != null) { + throw new IllegalStateException("Already bound to another GitHub instance."); + } + this.baseGitHub = github; + } + + protected synchronized final GitHub gitHub() { + if (gitHub == null) { + gitHub = new GitHub.AuthorizationRefreshGitHubWrapper(this.baseGitHub, authorizationProvider); + } + return gitHub; + } + } + private static class AuthorizationRefreshGitHubWrapper extends GitHub { private final AuthorizationProvider authorizationProvider; @@ -137,7 +181,11 @@ private static class AuthorizationRefreshGitHubWrapper extends GitHub { AuthorizationRefreshGitHubWrapper(GitHub github, AuthorizationProvider authorizationProvider) { super(github.client); this.authorizationProvider = authorizationProvider; - this.authorizationProvider.bind(this); + + // no dependent authorization providers nest like this currently, but they might in future + if (authorizationProvider instanceof DependentAuthorizationProvider) { + ((DependentAuthorizationProvider) authorizationProvider).bind(this); + } } @Nonnull diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 83df3c64e4..9ba5b91026 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; import org.apache.commons.io.IOUtils; -import org.kohsuke.github.authorization.ImmutableAuthorizationProvider.UserAuthorizationProvider; import org.kohsuke.github.authorization.AuthorizationProvider; +import org.kohsuke.github.authorization.UserAuthorizationProvider; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java index e9347c4ca7..4dd615885d 100644 --- a/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/AuthorizationProvider.java @@ -1,7 +1,5 @@ package org.kohsuke.github.authorization; -import org.kohsuke.github.GitHub; - import java.io.IOException; /** @@ -33,17 +31,6 @@ public interface AuthorizationProvider { */ String getEncodedAuthorization() throws IOException; - /** - * Binds this authorization provider to a github instance. - * - * Only needs to be implemented by dynamic credentials providers that use a github instance in order to refresh. - * - * @param github - * The github instance to be used for refreshing dynamic credentials - */ - default void bind(GitHub github) { - } - /** * A {@link AuthorizationProvider} that ensures that no credentials are returned */ diff --git a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java index 894564243c..41a113285a 100644 --- a/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/ImmutableAuthorizationProvider.java @@ -26,7 +26,7 @@ public ImmutableAuthorizationProvider(String authorization) { * oauthAccessToken */ public static AuthorizationProvider fromOauthToken(String oauthAccessToken) { - return new UserAuthorizationProvider(String.format("token %s", oauthAccessToken)); + return new UserProvider(String.format("token %s", oauthAccessToken)); } /** @@ -41,7 +41,7 @@ public static AuthorizationProvider fromOauthToken(String oauthAccessToken) { * oauthAccessToken */ public static AuthorizationProvider fromOauthToken(String oauthAccessToken, String login) { - return new UserAuthorizationProvider(String.format("token %s", oauthAccessToken), login); + return new UserProvider(String.format("token %s", oauthAccessToken), login); } /** @@ -84,7 +84,7 @@ public static AuthorizationProvider fromLoginAndPassword(String login, String pa String charsetName = StandardCharsets.UTF_8.name(); String b64encoded = Base64.getEncoder().encodeToString(authorization.getBytes(charsetName)); String encodedAuthorization = String.format("Basic %s", b64encoded); - return new UserAuthorizationProvider(encodedAuthorization, login); + return new UserProvider(encodedAuthorization, login); } catch (UnsupportedEncodingException e) { // If UTF-8 isn't supported, there are bigger problems throw new IllegalStateException("Could not generate encoded authorization", e); @@ -100,21 +100,22 @@ public String getEncodedAuthorization() { * An internal class representing all user-related credentials, which are credentials that have a login or should * query the user endpoint for the login matching this credential. */ - static class UserAuthorizationProvider extends ImmutableAuthorizationProvider { + private static class UserProvider extends ImmutableAuthorizationProvider implements UserAuthorizationProvider { private final String login; - UserAuthorizationProvider(String authorization) { + UserProvider(String authorization) { this(authorization, null); } - UserAuthorizationProvider(String authorization, String login) { + UserProvider(String authorization, String login) { super(authorization); this.login = login; } @CheckForNull - String getLogin() { + @Override + public String getLogin() { return login; } diff --git a/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java index c3772b43eb..020725fb41 100644 --- a/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java +++ b/src/main/java/org/kohsuke/github/authorization/OrgAppInstallationAuthorizationProvider.java @@ -4,7 +4,6 @@ import org.kohsuke.github.GHAppInstallation; import org.kohsuke.github.GHAppInstallationToken; import org.kohsuke.github.GitHub; -import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.IOException; import java.time.Duration; @@ -16,12 +15,8 @@ /** * Provides an AuthorizationProvider that performs automatic token refresh. */ -public class OrgAppInstallationAuthorizationProvider implements AuthorizationProvider { +public class OrgAppInstallationAuthorizationProvider extends GitHub.DependentAuthorizationProvider { - private GitHub baseGitHub; - private GitHub gitHub; - - private final AuthorizationProvider refreshProvider; private final String organizationName; private String latestToken; @@ -43,13 +38,8 @@ public class OrgAppInstallationAuthorizationProvider implements AuthorizationPro @Deprecated public OrgAppInstallationAuthorizationProvider(String organizationName, AuthorizationProvider authorizationProvider) { + super(authorizationProvider); this.organizationName = organizationName; - this.refreshProvider = authorizationProvider; - } - - @Override - public void bind(GitHub github) { - this.baseGitHub = github; } @Override @@ -63,10 +53,7 @@ public String getEncodedAuthorization() throws IOException { } private void refreshToken() throws IOException { - if (gitHub == null) { - gitHub = new GitHub.CredentialRefreshGitHubWrapper(this.baseGitHub, refreshProvider); - } - + GitHub gitHub = this.gitHub(); GHAppInstallation installationByOrganization = gitHub.getApp() .getInstallationByOrganization(this.organizationName); GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create(); diff --git a/src/main/java/org/kohsuke/github/authorization/UserAuthorizationProvider.java b/src/main/java/org/kohsuke/github/authorization/UserAuthorizationProvider.java new file mode 100644 index 0000000000..0d2c57751b --- /dev/null +++ b/src/main/java/org/kohsuke/github/authorization/UserAuthorizationProvider.java @@ -0,0 +1,22 @@ +package org.kohsuke.github.authorization; + +import javax.annotation.CheckForNull; + +/** + * Interface for all user-related authorization providers. + * + * {@link AuthorizationProvider}s can apply to a number of different account types. This interface applies to providers + * for user accounts, ones that have a login or should query the "/user" endpoint for the login matching this + * credential. + */ +public interface UserAuthorizationProvider extends AuthorizationProvider { + + /** + * Gets the user login name. + * + * @return the user login for this provider, or {@code null} if the login value should be queried from the "/user" + * endpoint. + */ + @CheckForNull + String getLogin(); +} diff --git a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java similarity index 98% rename from src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java rename to src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java index 74622b2c19..c8e8d7ddb6 100644 --- a/src/main/java/org/kohsuke/github/extras/auth/JWTTokenProvider.java +++ b/src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java @@ -1,4 +1,4 @@ -package org.kohsuke.github.extras.auth; +package org.kohsuke.github.extras.authorization; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; diff --git a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java index bc0dc92a60..74576ba58c 100644 --- a/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java +++ b/src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java @@ -3,7 +3,7 @@ import io.jsonwebtoken.Jwts; import org.apache.commons.io.IOUtils; import org.kohsuke.github.authorization.AuthorizationProvider; -import org.kohsuke.github.extras.auth.JWTTokenProvider; +import org.kohsuke.github.extras.authorization.JWTTokenProvider; import java.io.File; import java.io.IOException; diff --git a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java index 481933f95e..0fb168eae8 100644 --- a/src/test/java/org/kohsuke/github/GitHubConnectionTest.java +++ b/src/test/java/org/kohsuke/github/GitHubConnectionTest.java @@ -1,7 +1,7 @@ package org.kohsuke.github; import org.junit.Test; -import org.kohsuke.github.authorization.ImmutableAuthorizationProvider.UserAuthorizationProvider; +import org.kohsuke.github.authorization.UserAuthorizationProvider; import java.io.IOException; import java.lang.reflect.Field; diff --git a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java b/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java similarity index 97% rename from src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java rename to src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java index 7cca641c42..8c55558f96 100644 --- a/src/test/java/org/kohsuke/github/extras/auth/JWTTokenProviderTest.java +++ b/src/test/java/org/kohsuke/github/extras/authorization/JWTTokenProviderTest.java @@ -1,4 +1,4 @@ -package org.kohsuke.github.extras.auth; +package org.kohsuke.github.extras.authorization; import org.junit.Test; import org.kohsuke.github.AbstractGitHubWireMockTest; diff --git a/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json b/src/test/resources/org/kohsuke/github/extras/authorization/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json similarity index 100% rename from src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json rename to src/test/resources/org/kohsuke/github/extras/authorization/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/__files/app-1.json diff --git a/src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json b/src/test/resources/org/kohsuke/github/extras/authorization/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json similarity index 100% rename from src/test/resources/org/kohsuke/github/extras/auth/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json rename to src/test/resources/org/kohsuke/github/extras/authorization/JWTTokenProviderTest/wiremock/testAuthorizationHeaderPattern/mappings/app-1.json