diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java new file mode 100644 index 0000000000..0c9ec2202e --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsIterable.java @@ -0,0 +1,48 @@ +package org.kohsuke.github; + +import java.net.MalformedURLException; +import java.util.Iterator; + +import javax.annotation.Nonnull; + +/** + * Iterable for GHAppInstallation listing. + */ +class GHAppInstallationsIterable extends PagedIterable { + public static final String APP_INSTALLATIONS_URL = "/user/installations"; + private final transient GitHub root; + private GHAppInstallationsPage result; + + public GHAppInstallationsIterable(GitHub root) { + this.root = root; + } + + @Nonnull + @Override + public PagedIterator _iterator(int pageSize) { + try { + final GitHubRequest request = root.createRequest().withUrlPath(APP_INSTALLATIONS_URL).build(); + return new PagedIterator<>( + adapt(GitHubPageIterator.create(root.getClient(), GHAppInstallationsPage.class, request, pageSize)), + null); + } catch (MalformedURLException e) { + throw new GHException("Malformed URL", e); + } + } + + protected Iterator adapt(final Iterator base) { + return new Iterator() { + public boolean hasNext() { + return base.hasNext(); + } + + public GHAppInstallation[] next() { + GHAppInstallationsPage v = base.next(); + if (result == null) { + result = v; + } + return v.getInstallations(); + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java new file mode 100644 index 0000000000..14a978d527 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHAppInstallationsPage.java @@ -0,0 +1,17 @@ +package org.kohsuke.github; + +/** + * Represents the one page of GHAppInstallations. + */ +class GHAppInstallationsPage { + private int total_count; + private GHAppInstallation[] installations; + + public int getTotalCount() { + return total_count; + } + + GHAppInstallation[] getInstallations() { + return installations; + } +} diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index d827efb307..0cc749df35 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -239,4 +239,18 @@ public GHMembership getMembership(GHOrganization o) throws IOException { //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails"); // root.retrieveWithAuth3() // } + + /** + * Lists installations of your GitHub App that the authenticated user has explicit permission to access. You must + * use a user-to-server OAuth access token, created for a user who has authorized your GitHub App, to access this + * endpoint. + * + * @return the paged iterable + * @see List + * app installations accessible to the user access token + */ + public PagedIterable getAppInstallations() { + return new GHAppInstallationsIterable(root); + } } diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index 331f12eaa9..68f5379cad 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -384,6 +384,20 @@ public void testFetchPullRequestAsList() throws Exception { assertThat(prs, is(not(empty()))); } + @Test + public void testGetAppInstallations() throws Exception { + // To generate test data user-to-server OAuth access token was used + // For more details pls read + // https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#identifying-users-on-your-site + final PagedIterable appInstallation = gitHub.getMyself().getAppInstallations(); + + assertThat(appInstallation.toList(), is(not(empty()))); + assertThat(appInstallation.toList().size(), is(1)); + final GHAppInstallation ghAppInstallation = appInstallation.toList().get(0); + assertThat(ghAppInstallation.getAppId(), is(122478L)); + assertThat(ghAppInstallation.getAccount().getLogin(), is("t0m4uk1991")); + } + @Ignore("Needs mocking check") @Test public void testRepoPermissions() throws Exception { diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json new file mode 100644 index 0000000000..6929091fa2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-1.json @@ -0,0 +1,34 @@ +{ + "login": "t0m4uk1991", + "id": 6698785, + "node_id": "MDQ6VXNlcjY2OTg3ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/t0m4uk1991", + "html_url": "https://github.com/t0m4uk1991", + "followers_url": "https://api.github.com/users/t0m4uk1991/followers", + "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}", + "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}", + "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions", + "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs", + "repos_url": "https://api.github.com/users/t0m4uk1991/repos", + "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}", + "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events", + "type": "User", + "site_admin": false, + "name": null, + "company": null, + "blog": "https://t0m4uk1991.github.io", + "location": "Ukraine", + "email": "t0m4uk1991@gmail.com", + "hireable": true, + "bio": null, + "twitter_username": null, + "public_repos": 14, + "public_gists": 10, + "followers": 2, + "following": 2, + "created_at": "2014-02-16T20:43:03Z", + "updated_at": "2021-06-21T14:53:32Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json new file mode 100644 index 0000000000..6929091fa2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user-2.json @@ -0,0 +1,34 @@ +{ + "login": "t0m4uk1991", + "id": 6698785, + "node_id": "MDQ6VXNlcjY2OTg3ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/t0m4uk1991", + "html_url": "https://github.com/t0m4uk1991", + "followers_url": "https://api.github.com/users/t0m4uk1991/followers", + "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}", + "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}", + "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions", + "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs", + "repos_url": "https://api.github.com/users/t0m4uk1991/repos", + "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}", + "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events", + "type": "User", + "site_admin": false, + "name": null, + "company": null, + "blog": "https://t0m4uk1991.github.io", + "location": "Ukraine", + "email": "t0m4uk1991@gmail.com", + "hireable": true, + "bio": null, + "twitter_username": null, + "public_repos": 14, + "public_gists": 10, + "followers": 2, + "following": 2, + "created_at": "2014-02-16T20:43:03Z", + "updated_at": "2021-06-21T14:53:32Z" +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json new file mode 100644 index 0000000000..908e9ca83d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/__files/user_installations-3.json @@ -0,0 +1,48 @@ +{ + "total_count": 1, + "installations": [ + { + "id": 17761138, + "account": { + "login": "t0m4uk1991", + "id": 6698785, + "node_id": "MDQ6VXNlcjY2OTg3ODU=", + "avatar_url": "https://avatars.githubusercontent.com/u/6698785?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/t0m4uk1991", + "html_url": "https://github.com/t0m4uk1991", + "followers_url": "https://api.github.com/users/t0m4uk1991/followers", + "following_url": "https://api.github.com/users/t0m4uk1991/following{/other_user}", + "gists_url": "https://api.github.com/users/t0m4uk1991/gists{/gist_id}", + "starred_url": "https://api.github.com/users/t0m4uk1991/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/t0m4uk1991/subscriptions", + "organizations_url": "https://api.github.com/users/t0m4uk1991/orgs", + "repos_url": "https://api.github.com/users/t0m4uk1991/repos", + "events_url": "https://api.github.com/users/t0m4uk1991/events{/privacy}", + "received_events_url": "https://api.github.com/users/t0m4uk1991/received_events", + "type": "User", + "site_admin": false + }, + "repository_selection": "selected", + "access_tokens_url": "https://api.github.com/app/installations/17761138/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/settings/installations/17761138", + "app_id": 122478, + "app_slug": "hub4j-github-api-test", + "target_id": 6698785, + "target_type": "User", + "permissions": { + "metadata": "read", + "administration": "read" + }, + "events": [], + "created_at": "2021-06-21T17:39:33.000+03:00", + "updated_at": "2021-06-21T17:39:33.000+03:00", + "single_file_name": null, + "has_multiple_single_files": false, + "single_file_paths": [], + "suspended_by": null, + "suspended_at": null + } + ] +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json new file mode 100644 index 0000000000..aef8226f9c --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-1.json @@ -0,0 +1,51 @@ +{ + "id": "04c78e0b-2ca2-4f06-b281-956eaba032bd", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "user-1.json", + "headers": { + "Server": "GitHub.com", + "Date": "Mon, 21 Jun 2021 20:51:32 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"e9e4780f1b19e9323e73a0f5690bf7b59ee956f536ee5a303eb8657423123051\"", + "Last-Modified": "Mon, 21 Jun 2021 14:53:32 GMT", + "X-OAuth-Scopes": "", + "X-Accepted-OAuth-Scopes": "", + "x-oauth-client-id": "Iv1.d1d44ef7894694c0", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4999", + "X-RateLimit-Reset": "1624312292", + "X-RateLimit-Used": "1", + "X-RateLimit-Resource": "core", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "2B69:2FF5:2E0782:302E02:60D0FBD4" + } + }, + "uuid": "04c78e0b-2ca2-4f06-b281-956eaba032bd", + "persistent": true, + "scenarioName": "scenario-1-user", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-user-2", + "insertionIndex": 1 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json new file mode 100644 index 0000000000..a98a10990b --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user-2.json @@ -0,0 +1,50 @@ +{ + "id": "c209281c-5bd1-405a-9dc7-34227ec4d5ae", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "user-2.json", + "headers": { + "Server": "GitHub.com", + "Date": "Mon, 21 Jun 2021 20:51:33 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"e9e4780f1b19e9323e73a0f5690bf7b59ee956f536ee5a303eb8657423123051\"", + "Last-Modified": "Mon, 21 Jun 2021 14:53:32 GMT", + "X-OAuth-Scopes": "", + "X-Accepted-OAuth-Scopes": "", + "x-oauth-client-id": "Iv1.d1d44ef7894694c0", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4997", + "X-RateLimit-Reset": "1624312292", + "X-RateLimit-Used": "3", + "X-RateLimit-Resource": "core", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "04D3:4B1B:1F3F8F:200584:60D0FBD4" + } + }, + "uuid": "c209281c-5bd1-405a-9dc7-34227ec4d5ae", + "persistent": true, + "scenarioName": "scenario-1-user", + "requiredScenarioState": "scenario-1-user-2", + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json new file mode 100644 index 0000000000..29af79a9d1 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testGetAppInstallations/mappings/user_installations-3.json @@ -0,0 +1,47 @@ +{ + "id": "179e0b84-8037-4d0b-9833-c8ef1b226592", + "name": "user_installations", + "request": { + "url": "/user/installations", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "user_installations-3.json", + "headers": { + "Server": "GitHub.com", + "Date": "Mon, 21 Jun 2021 20:51:33 GMT", + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"05762b67c1a69e46ee710171fdb32872b44904cc7c68a7617c134a6c5eee2b13\"", + "X-OAuth-Scopes": "", + "X-Accepted-OAuth-Scopes": "", + "x-oauth-client-id": "Iv1.d1d44ef7894694c0", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4996", + "X-RateLimit-Reset": "1624312292", + "X-RateLimit-Used": "4", + "X-RateLimit-Resource": "core", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "0", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "0A50:20B4:B38D2B:B6B016:60D0FBD5" + } + }, + "uuid": "179e0b84-8037-4d0b-9833-c8ef1b226592", + "persistent": true, + "insertionIndex": 3 +} \ No newline at end of file