Skip to content

Commit a4da14a

Browse files
authored
Merge branch 'main' into implement_apps
2 parents bb008f5 + 5380f59 commit a4da14a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+50873
-32
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@
704704
<plugin>
705705
<groupId>org.apache.maven.plugins</groupId>
706706
<artifactId>maven-enforcer-plugin</artifactId>
707-
<version>3.1.0</version>
707+
<version>3.2.1</version>
708708
<executions>
709709
<execution>
710710
<id>enforce-jacoco-exist</id>

src/main/java/org/kohsuke/github/GHAppInstallation.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,25 @@ public GHAppCreateTokenBuilder createToken(Map<String, GHPermissionType> permiss
361361
public GHAppCreateTokenBuilder createToken() {
362362
return new GHAppCreateTokenBuilder(root(), String.format("/app/installations/%d/access_tokens", getId()));
363363
}
364+
365+
/**
366+
* Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub
367+
* App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will
368+
* also see the upcoming pending change.
369+
*
370+
* <p>
371+
* GitHub Apps must use a JWT to access this endpoint.
372+
* <p>
373+
* OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint.
374+
*
375+
* @return a GHMarketplaceAccountPlan instance
376+
* @throws IOException
377+
* it may throw an {@link IOException}
378+
* @see <a href=
379+
* "https://docs.github.com/en/rest/apps/marketplace?apiVersion=2022-11-28#get-a-subscription-plan-for-an-account">Get
380+
* a subscription plan for an account</a>
381+
*/
382+
public GHMarketplaceAccountPlan getMarketplaceAccount() throws IOException {
383+
return new GHMarketplacePlanForAccountBuilder(root(), account.getId()).createRequest();
384+
}
364385
}

src/main/java/org/kohsuke/github/GHMarketplaceAccount.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.kohsuke.github;
22

3+
import java.io.IOException;
34
import java.net.URL;
45

56
// TODO: Auto-generated Javadoc
@@ -72,4 +73,25 @@ public GHMarketplaceAccountType getType() {
7273
return type;
7374
}
7475

76+
/**
77+
* Shows whether the user or organization account actively subscribes to a plan listed by the authenticated GitHub
78+
* App. When someone submits a plan change that won't be processed until the end of their billing cycle, you will
79+
* also see the upcoming pending change.
80+
*
81+
* <p>
82+
* GitHub Apps must use a JWT to access this endpoint.
83+
* <p>
84+
* OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint.
85+
*
86+
* @return a GHMarketplaceListAccountBuilder instance
87+
* @throws IOException
88+
* in case of {@link IOException}
89+
* @see <a href=
90+
* "https://docs.github.com/en/rest/apps/marketplace?apiVersion=2022-11-28#get-a-subscription-plan-for-an-account">Get
91+
* a subscription plan for an account</a>
92+
*/
93+
public GHMarketplaceAccountPlan getPlan() throws IOException {
94+
return new GHMarketplacePlanForAccountBuilder(root(), this.id).createRequest();
95+
}
96+
7597
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.kohsuke.github;
2+
3+
import java.io.IOException;
4+
5+
// TODO: Auto-generated Javadoc
6+
/**
7+
* Returns the plan associated with current account.
8+
*
9+
* @author Benoit Lacelle
10+
* @see GHMarketplacePlan#listAccounts()
11+
* @see GitHub#listMarketplacePlans()
12+
*/
13+
public class GHMarketplacePlanForAccountBuilder extends GitHubInteractiveObject {
14+
private final Requester builder;
15+
private final long accountId;
16+
17+
/**
18+
* Instantiates a new GH marketplace list account builder.
19+
*
20+
* @param root
21+
* the root
22+
* @param accountId
23+
* the account id
24+
*/
25+
GHMarketplacePlanForAccountBuilder(GitHub root, long accountId) {
26+
super(root);
27+
this.builder = root.createRequest();
28+
this.accountId = accountId;
29+
}
30+
31+
/**
32+
* Fetch the plan associated with the account specified on construction.
33+
* <p>
34+
* GitHub Apps must use a JWT to access this endpoint.
35+
*
36+
* @return a GHMarketplaceAccountPlan
37+
* @throws IOException
38+
* on error
39+
*/
40+
public GHMarketplaceAccountPlan createRequest() throws IOException {
41+
return builder.withUrlPath(String.format("/marketplace_listing/accounts/%d", this.accountId))
42+
.fetch(GHMarketplaceAccountPlan.class);
43+
}
44+
45+
}

src/main/java/org/kohsuke/github/GHPullRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ public int getAdditions() throws IOException {
242242
}
243243

244244
/**
245-
* Gets commits.
245+
* Gets the number of commits.
246246
*
247-
* @return the commits
247+
* @return the number of commits
248248
* @throws IOException
249249
* the io exception
250250
*/

src/main/java/org/kohsuke/github/GitHub.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,23 @@ public List<GHEventInfo> getEvents() throws IOException {
915915
return createRequest().withUrlPath("/events").toIterable(GHEventInfo[].class, null).toList();
916916
}
917917

918+
/**
919+
* List public events for a user
920+
* <a href="https://docs.github.com/en/rest/activity/events?apiVersion=2022-11-28#list-public-events-for-a-user">see
921+
* API documentation</a>
922+
*
923+
* @param login
924+
* the login (user) to look public events for
925+
* @return the events
926+
* @throws IOException
927+
* the io exception
928+
*/
929+
public List<GHEventInfo> getUserPublicEvents(String login) throws IOException {
930+
return createRequest().withUrlPath("/users/" + login + "/events/public")
931+
.toIterable(GHEventInfo[].class, null)
932+
.toList();
933+
}
934+
918935
/**
919936
* Gets a single gist by ID.
920937
*

src/main/java/org/kohsuke/github/extras/authorization/JWTTokenProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.jsonwebtoken.JwtBuilder;
44
import io.jsonwebtoken.Jwts;
55
import io.jsonwebtoken.SignatureAlgorithm;
6+
import io.jsonwebtoken.jackson.io.JacksonSerializer;
67
import org.kohsuke.github.authorization.AuthorizationProvider;
78

89
import java.io.File;
@@ -181,7 +182,7 @@ private String refreshJWT() {
181182
validUntil = expiration.minus(Duration.ofMinutes(2));
182183

183184
// Builds the JWT and serializes it to a compact, URL-safe string
184-
return builder.compact();
185+
return builder.serializeToJsonWith(new JacksonSerializer<>()).compact();
185186
}
186187

187188
Instant getIssuedAt(Instant now) {

src/site/apt/index.apt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ Pluggable HTTP client
140140

141141
This library comes with a pluggable connector to use different HTTP client implementations
142142
through <<<HttpConnector>>>. In particular, this means you can use {{{https://square.github.io/okhttp/}OkHttp}},
143-
so we can make use of it's HTTP response cache.
143+
so we can make use of its HTTP response cache.
144144
Making a conditional request against the GitHub API and receiving a 304 response
145-
{{{https://docs.github.com/en/rest/overview/resources-in-the-rest-api#conditional-requests}does not count against the rate limit}}.
145+
{{{https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#conditional-requests}does not count against the rate limit}}.
146146

147147
The following code shows an example of how to set up persistent cache on the disk:
148148

src/test/java/org/kohsuke/github/AbstractGHAppInstallationTest.java

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.kohsuke.github;
22

3+
import com.google.common.collect.ImmutableSet;
34
import io.jsonwebtoken.Jwts;
45
import org.apache.commons.io.IOUtils;
56
import org.kohsuke.github.authorization.AuthorizationProvider;
@@ -9,6 +10,7 @@
910
import java.io.IOException;
1011
import java.nio.charset.StandardCharsets;
1112
import java.nio.file.Files;
13+
import java.nio.file.Paths;
1214
import java.security.GeneralSecurityException;
1315
import java.security.KeyFactory;
1416
import java.security.PrivateKey;
@@ -17,13 +19,19 @@
1719
import java.time.temporal.ChronoUnit;
1820
import java.util.Base64;
1921
import java.util.Date;
22+
import java.util.List;
2023

2124
// TODO: Auto-generated Javadoc
2225
/**
2326
* The Class AbstractGHAppInstallationTest.
2427
*/
2528
public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
2629

30+
private static String ENV_GITHUB_APP_ID = "GITHUB_APP_ID";
31+
private static String ENV_GITHUB_APP_JWK_PATH = "GITHUB_APP_JWK_PATH";
32+
private static String ENV_GITHUB_APP_ORG = "GITHUB_APP_ORG";
33+
private static String ENV_GITHUB_APP_REPO = "GITHUB_APP_REPO";
34+
2735
private static String TEST_APP_ID_1 = "82994";
2836
private static String TEST_APP_ID_2 = "83009";
2937
private static String TEST_APP_ID_3 = "89368";
@@ -44,16 +52,23 @@ public class AbstractGHAppInstallationTest extends AbstractGitHubWireMockTest {
4452
* Instantiates a new abstract GH app installation test.
4553
*/
4654
protected AbstractGHAppInstallationTest() {
55+
String appId = System.getenv(ENV_GITHUB_APP_ID);
56+
String appJwkPath = System.getenv(ENV_GITHUB_APP_JWK_PATH);
4757
try {
48-
jwtProvider1 = new JWTTokenProvider(TEST_APP_ID_1,
49-
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_1).getFile()));
50-
jwtProvider2 = new JWTTokenProvider(TEST_APP_ID_2,
51-
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile()).toPath());
52-
jwtProvider3 = new JWTTokenProvider(TEST_APP_ID_3,
53-
new String(
54-
Files.readAllBytes(
55-
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_3).getFile()).toPath()),
56-
StandardCharsets.UTF_8));
58+
if (appId != null && appJwkPath != null) {
59+
jwtProvider1 = new JWTTokenProvider(appId, Paths.get(appJwkPath));
60+
jwtProvider2 = jwtProvider1;
61+
jwtProvider3 = jwtProvider1;
62+
} else {
63+
jwtProvider1 = new JWTTokenProvider(TEST_APP_ID_1,
64+
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_1).getFile()));
65+
jwtProvider2 = new JWTTokenProvider(TEST_APP_ID_2,
66+
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_2).getFile()).toPath());
67+
jwtProvider3 = new JWTTokenProvider(TEST_APP_ID_3,
68+
new String(Files.readAllBytes(
69+
new File(this.getClass().getResource(PRIVATE_KEY_FILE_APP_3).getFile()).toPath()),
70+
StandardCharsets.UTF_8));
71+
}
5772
} catch (GeneralSecurityException | IOException e) {
5873
throw new RuntimeException("These should never fail", e);
5974
}
@@ -89,17 +104,28 @@ private String createJwtToken(String keyFileResouceName, String appId) {
89104
* Signals that an I/O exception has occurred.
90105
*/
91106
protected GHAppInstallation getAppInstallationWithToken(String jwtToken) throws IOException {
107+
if (jwtToken.startsWith("Bearer ")) {
108+
jwtToken = jwtToken.substring("Bearer ".length());
109+
}
110+
92111
GitHub gitHub = getGitHubBuilder().withJwtToken(jwtToken)
93112
.withEndpoint(mockGitHub.apiServer().baseUrl())
94113
.build();
95114

96-
GHAppInstallation appInstallation = gitHub.getApp()
97-
.listInstallations()
98-
.toList()
99-
.stream()
100-
.filter(it -> it.getAccount().login.equals("hub4j-test-org"))
101-
.findFirst()
102-
.get();
115+
GHApp app = gitHub.getApp();
116+
117+
GHAppInstallation appInstallation;
118+
if (ImmutableSet.of(TEST_APP_ID_1, TEST_APP_ID_2, TEST_APP_ID_3).contains(Long.toString(app.getId()))) {
119+
List<GHAppInstallation> installations = app.listInstallations().toList();
120+
appInstallation = installations.stream()
121+
.filter(it -> it.getAccount().login.equals("hub4j-test-org"))
122+
.findFirst()
123+
.get();
124+
} else {
125+
// We may be processing a custom JWK, for a custom GHApp: fetch a relevant repository dynamically
126+
appInstallation = app.getInstallationByRepository(System.getenv(ENV_GITHUB_APP_ORG),
127+
System.getenv(ENV_GITHUB_APP_REPO));
128+
}
103129

104130
// TODO: this is odd
105131
// appInstallation

src/test/java/org/kohsuke/github/AppTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,30 @@ public void testEventApi() throws Exception {
10361036
}
10371037
}
10381038

1039+
/**
1040+
* Test user public event api.
1041+
*
1042+
* @throws Exception
1043+
* the exception
1044+
*/
1045+
@Test
1046+
public void testUserPublicEventApi() throws Exception {
1047+
for (GHEventInfo ev : gitHub.getUserPublicEvents("PierreBtz")) {
1048+
if (ev.getType() == GHEvent.PULL_REQUEST) {
1049+
if (ev.getId() == 27449881624L) {
1050+
assertThat(ev.getActorLogin(), equalTo("PierreBtz"));
1051+
assertThat(ev.getOrganization().getLogin(), equalTo("hub4j"));
1052+
assertThat(ev.getRepository().getFullName(), equalTo("hub4j/github-api"));
1053+
assertThat(ev.getCreatedAt(), equalTo(GitHubClient.parseDate("2023-03-02T16:37:49Z")));
1054+
assertThat(ev.getType(), equalTo(GHEvent.PULL_REQUEST));
1055+
}
1056+
1057+
GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class);
1058+
assertThat(pr.getNumber(), is(pr.getPullRequest().getNumber()));
1059+
}
1060+
}
1061+
}
1062+
10391063
/**
10401064
* Test app.
10411065
*

0 commit comments

Comments
 (0)