From e7a95d52ee30da187a52edd6b0939b0d071fb9a9 Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Wed, 28 Jun 2023 15:38:40 +1000 Subject: [PATCH 1/4] [JENKINS-66661] Add tests for ForkPR trust strategies --- .../GitHubSCMSourceTest.java | 222 ++++++++++++++++++ ...dy-cloudbeers-yolo-collaborators-none.json | 1 + ...ollaborators-stephenc-permission-read.json | 32 +++ ...olo-collaborators-stephenc-permission.json | 32 +++ .../body-cloudbeers-yolo-collaborators.json | 58 +++++ .../api/__files/body-yolo-contents-B10ef.json | 61 +++++ .../api/__files/body-yolo-contents-GGKvD.json | 61 +++++ .../api/__files/body-yolo-contents-cw7Ms.json | 61 +++++ .../api/__files/body-yolo-contents-h9pJz.json | 61 +++++ .../mappings/mapping-yolo-contents-B1Oef.json | 34 +++ .../mappings/mapping-yolo-contents-GGKvD.json | 34 +++ .../mappings/mapping-yolo-contents-cw7Ms.json | 34 +++ .../mappings/mapping-yolo-contents-h9pJz.json | 34 +++ 13 files changed, 725 insertions(+) create mode 100644 src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-none.json create mode 100644 src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission-read.json create mode 100644 src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission.json create mode 100644 src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json create mode 100644 src/test/resources/api/__files/body-yolo-contents-B10ef.json create mode 100644 src/test/resources/api/__files/body-yolo-contents-GGKvD.json create mode 100644 src/test/resources/api/__files/body-yolo-contents-cw7Ms.json create mode 100644 src/test/resources/api/__files/body-yolo-contents-h9pJz.json create mode 100644 src/test/resources/api/mappings/mapping-yolo-contents-B1Oef.json create mode 100644 src/test/resources/api/mappings/mapping-yolo-contents-GGKvD.json create mode 100644 src/test/resources/api/mappings/mapping-yolo-contents-cw7Ms.json create mode 100644 src/test/resources/api/mappings/mapping-yolo-contents-h9pJz.json diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java index b91212b9f..214a17e43 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java @@ -67,6 +67,7 @@ import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import javax.servlet.http.HttpServletResponse; import jenkins.branch.BranchSource; import jenkins.model.Jenkins; import jenkins.plugins.git.GitSCMSource; @@ -716,6 +717,227 @@ public void getTrustedRevisionReturnsRevisionIfRepoOwnerAndPullRequestBranchOwne sameInstance(revision)); } + @Test + public void fetchForkPRFromCollaborators() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()))); + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators")) + .inScenario("PR from Fork not Collaborators") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-cloudbeers-yolo-collaborators.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Source owner is a collaborator, the trusted revision must be the PR head / merge + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision(revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(revision)); + } + } + + @Test + public void fetchForkPRCannotFindCollaborators() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()))); + for (Integer errorCode : new int[] { + HttpServletResponse.SC_NOT_FOUND, HttpServletResponse.SC_UNAUTHORIZED, HttpServletResponse.SC_FORBIDDEN + }) { + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators")) + .inScenario("PR from Fork " + errorCode + " Collaborators") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse().withStatus(errorCode))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) + (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Cannot find collaborators, the trusted revision must be the base (target) revision, not the head + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision( + revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(((PullRequestSCMRevision) revision).getTarget())); + } + } + } + + @Test + public void fetchForkPRFromNonCollaborators() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), + new ForkPullRequestDiscoveryTrait.TrustContributors()))); + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators")) + .inScenario("PR from Fork not Collaborators") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-cloudbeers-yolo-collaborators-none.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Source owner is not a collaborator, the trusted revision must be the base (target) revision, not the head + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision(revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(((PullRequestSCMRevision) revision).getTarget())); + } + } + + @Test + public void fetchForkPRWithPermissions() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustPermission()))); + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators/stephenc/permission")) + .inScenario("PR from Fork with Permissions") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-cloudbeers-yolo-collaborators-stephenc-permission.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Source owner has sufficient permissions, the trusted revision must be the PR head / merge + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision(revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(revision)); + } + } + + @Test + public void fetchForkPRCannotFindPermissions() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustPermission()))); + for (Integer errorCode : new int[] { + HttpServletResponse.SC_NOT_FOUND, HttpServletResponse.SC_UNAUTHORIZED, HttpServletResponse.SC_FORBIDDEN + }) { + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators/stephenc/permission")) + .inScenario("PR from Fork " + errorCode + " Permissions") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse().withStatus(errorCode))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) + (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Cannot find collaborators, the trusted revision must be the base (target) revision, not the head + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision( + revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(((PullRequestSCMRevision) revision).getTarget())); + } + } + } + + @Test + public void fetchForkPRInsufficientPermissions() throws IOException, InterruptedException { + source.setTraits(List.of(new ForkPullRequestDiscoveryTrait( + EnumSet.of(ChangeRequestCheckoutStrategy.MERGE), new ForkPullRequestDiscoveryTrait.TrustPermission()))); + githubApi.stubFor(get(urlMatching("(/api/v3)?/repos/cloudbeers/yolo/collaborators/stephenc/permission")) + .inScenario("PR from Fork Permissions") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("body-cloudbeers-yolo-collaborators-stephenc-permission-read.json"))); + + SCMHeadObserver.Collector collector = SCMHeadObserver.collect(); + source.fetch( + (SCMSourceCriteria) (probe, listener) -> probe.stat("README.md").getType() == SCMFile.Type.REGULAR_FILE, + collector, + null, + null); + + Map byName = new HashMap<>(); + Map revByName = new HashMap<>(); + for (Map.Entry h : collector.result().entrySet()) { + byName.put(h.getKey().getName(), h.getKey()); + revByName.put(h.getKey().getName(), h.getValue()); + } + + assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); + + // Cannot find collaborators, the trusted revision must be the base (target) revision, not the head + for (SCMRevision revision : revByName.values()) { + assertThat( + source.getTrustedRevision(revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), + is(((PullRequestSCMRevision) revision).getTarget())); + } + } + private PullRequestSCMRevision createRevision(String sourceOwner) { PullRequestSCMHead head = new PullRequestSCMHead( "", diff --git a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-none.json b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-none.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-none.json @@ -0,0 +1 @@ +[] diff --git a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission-read.json b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission-read.json new file mode 100644 index 000000000..4bae092f8 --- /dev/null +++ b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission-read.json @@ -0,0 +1,32 @@ +{ + "permission": "read", + "user": { + "login": "stephenc", + "id": 209336, + "node_id": "MjA5MzM2OlVzZXIyMDkzMzYK", + "avatar_url": "https://avatars.githubusercontent.com/u/209336?v=3", + "gravatar_id": "", + "url": "https://api.github.com//users/stephenc", + "html_url": "https://api.github.com/stephenc", + "followers_url": "https://api.github.com//users/stephenc/followers", + "following_url": "https://api.github.com//users/stephenc/following{/other_user}", + "gists_url": "https://api.github.com//users/stephenc/gists{/gist_id}", + "starred_url": "https://api.github.com//users/stephenc/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com//users/stephenc/subscriptions", + "organizations_url": "https://api.github.com//users/stephenc/orgs", + "repos_url": "https://api.github.com//users/stephenc/repos", + "events_url": "https://api.github.com//users/stephenc/events{/privacy}", + "received_events_url": "https://api.github.com//users/stephenc/received_events", + "type": "User", + "site_admin": false, + "permissions": { + "admin": false, + "maintain": false, + "push": false, + "triage": false, + "pull": true + }, + "role_name": "read" + }, + "role_name": "read" +} diff --git a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission.json b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission.json new file mode 100644 index 000000000..d32cf8712 --- /dev/null +++ b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators-stephenc-permission.json @@ -0,0 +1,32 @@ +{ + "permission": "write", + "user": { + "login": "stephenc", + "id": 209336, + "node_id": "MjA5MzM2OlVzZXIyMDkzMzYK", + "avatar_url": "https://avatars.githubusercontent.com/u/209336?v=3", + "gravatar_id": "", + "url": "https://api.github.com//users/stephenc", + "html_url": "https://api.github.com/stephenc", + "followers_url": "https://api.github.com//users/stephenc/followers", + "following_url": "https://api.github.com//users/stephenc/following{/other_user}", + "gists_url": "https://api.github.com//users/stephenc/gists{/gist_id}", + "starred_url": "https://api.github.com//users/stephenc/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com//users/stephenc/subscriptions", + "organizations_url": "https://api.github.com//users/stephenc/orgs", + "repos_url": "https://api.github.com//users/stephenc/repos", + "events_url": "https://api.github.com//users/stephenc/events{/privacy}", + "received_events_url": "https://api.github.com//users/stephenc/received_events", + "type": "User", + "site_admin": false, + "permissions": { + "admin": false, + "maintain": false, + "push": true, + "triage": true, + "pull": true + }, + "role_name": "write" + }, + "role_name": "write" +} diff --git a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json new file mode 100644 index 000000000..c540cfc28 --- /dev/null +++ b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json @@ -0,0 +1,58 @@ +[ + { + "login": "stephenc", + "id": 209336, + "node_id": "MjA5MzM2OlVzZXIyMDkzMzYK", + "avatar_url": "https://api.github.com/avatars/u/4?", + "gravatar_id": "", + "url": "https://api.github.com//users/stephenc", + "html_url": "https://api.github.com/stephenc", + "followers_url": "https://api.github.com//users/stephenc/followers", + "following_url": "https://api.github.com//users/stephenc/following{/other_user}", + "gists_url": "https://api.github.com//users/stephenc/gists{/gist_id}", + "starred_url": "https://api.github.com//users/stephenc/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com//users/stephenc/subscriptions", + "organizations_url": "https://api.github.com//users/stephenc/orgs", + "repos_url": "https://api.github.com//users/stephenc/repos", + "events_url": "https://api.github.com//users/stephenc/events{/privacy}", + "received_events_url": "https://api.github.com//users/stephenc/received_events", + "type": "User", + "site_admin": false, + "permissions": { + "admin": false, + "maintain": false, + "push": true, + "triage": true, + "pull": true + }, + "role_name": "read" + }, + { + "login": "cloudbeers", + "id": 4181899, + "node_id": "NDE4MTg5OTpVc2VyNDE4MTg5OQo=", + "avatar_url": "https://avatars.githubusercontent.com/u/4181899?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/cloudbeers", + "html_url": "https://api.github.com/cloudbeers", + "followers_url": "https://api.github.com/users/cloudbeers/followers", + "following_url": "https://api.github.com/users/cloudbeers/following{/other_user}", + "gists_url": "https://api.github.com/users/cloudbeers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cloudbeers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cloudbeers/subscriptions", + "organizations_url": "https://api.github.com/users/cloudbeers/orgs", + "repos_url": "https://api.github.com/users/cloudbeers/repos", + "events_url": "https://api.github.com/users/cloudbeers/events{/privacy}", + "received_events_url": "https://api.github.com/users/cloudbeers/received_events", + "type": "User", + "site_admin": false, + "permissions": { + "admin": true, + "maintain": true, + "push": true, + "triage": true, + "pull": true + }, + "role_name": "admin" + } +] diff --git a/src/test/resources/api/__files/body-yolo-contents-B10ef.json b/src/test/resources/api/__files/body-yolo-contents-B10ef.json new file mode 100644 index 000000000..1ab36426e --- /dev/null +++ b/src/test/resources/api/__files/body-yolo-contents-B10ef.json @@ -0,0 +1,61 @@ +[{ + "name": "CONTRIBUTING.adoc", + "path": "CONTRIBUTING.adoc", + "sha": "3d9a2d7e6e24f8689dc2daf77628e857389473da", + "size": 45, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/3/merge", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/merge/CONTRIBUTING.adoc", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/3/merge/CONTRIBUTING.adoc", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/3/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/merge/CONTRIBUTING.adoc" + } +}, { + "name": "README.md", + "path": "README.md", + "sha": "a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "size": 67, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/3/merge", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/merge/README.md", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/3/merge/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/3/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/merge/README.md" + } +}, { + "name": "foo", + "path": "foo", + "sha": "ed39365664d3b0d96f427b18a893de1883a276e4", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/3/merge", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/merge/foo", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/3/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/merge/foo" + } +}, { + "name": "fu", + "path": "fu", + "sha": "aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/3/merge", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/merge/fu", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/3/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/merge/fu" + } +}] diff --git a/src/test/resources/api/__files/body-yolo-contents-GGKvD.json b/src/test/resources/api/__files/body-yolo-contents-GGKvD.json new file mode 100644 index 000000000..daa92794c --- /dev/null +++ b/src/test/resources/api/__files/body-yolo-contents-GGKvD.json @@ -0,0 +1,61 @@ +[{ + "name": "CONTRIBUTING.adoc", + "path": "CONTRIBUTING.adoc", + "sha": "3d9a2d7e6e24f8689dc2daf77628e857389473da", + "size": 45, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/4/merge", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/merge/CONTRIBUTING.adoc", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/4/merge/CONTRIBUTING.adoc", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/4/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/merge/CONTRIBUTING.adoc" + } +}, { + "name": "README.md", + "path": "README.md", + "sha": "a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "size": 67, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/4/merge", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/merge/README.md", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/4/merge/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/4/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/merge/README.md" + } +}, { + "name": "foo", + "path": "foo", + "sha": "ed39365664d3b0d96f427b18a893de1883a276e4", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/4/merge", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/merge/foo", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/4/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/merge/foo" + } +}, { + "name": "fu", + "path": "fu", + "sha": "aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/4/merge", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/merge/fu", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/4/merge", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/merge/fu" + } +}] diff --git a/src/test/resources/api/__files/body-yolo-contents-cw7Ms.json b/src/test/resources/api/__files/body-yolo-contents-cw7Ms.json new file mode 100644 index 000000000..a96ea094a --- /dev/null +++ b/src/test/resources/api/__files/body-yolo-contents-cw7Ms.json @@ -0,0 +1,61 @@ +[{ + "name": "CONTRIBUTING.adoc", + "path": "CONTRIBUTING.adoc", + "sha": "3d9a2d7e6e24f8689dc2daf77628e857389473da", + "size": 45, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/3/head", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/head/CONTRIBUTING.adoc", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/3/head/CONTRIBUTING.adoc", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/3/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/head/CONTRIBUTING.adoc" + } +}, { + "name": "README.md", + "path": "README.md", + "sha": "a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "size": 67, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/3/head", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/head/README.md", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/3/head/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/3/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/3/head/README.md" + } +}, { + "name": "foo", + "path": "foo", + "sha": "ed39365664d3b0d96f427b18a893de1883a276e4", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/3/head", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/head/foo", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/3/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/head/foo" + } +}, { + "name": "fu", + "path": "fu", + "sha": "aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/3/head", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/head/fu", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/3/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/3/head/fu" + } +}] diff --git a/src/test/resources/api/__files/body-yolo-contents-h9pJz.json b/src/test/resources/api/__files/body-yolo-contents-h9pJz.json new file mode 100644 index 000000000..df07fbb6b --- /dev/null +++ b/src/test/resources/api/__files/body-yolo-contents-h9pJz.json @@ -0,0 +1,61 @@ +[{ + "name": "CONTRIBUTING.adoc", + "path": "CONTRIBUTING.adoc", + "sha": "3d9a2d7e6e24f8689dc2daf77628e857389473da", + "size": 45, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/4/head", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/head/CONTRIBUTING.adoc", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/4/head/CONTRIBUTING.adoc", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/CONTRIBUTING.adoc?ref=refs/pull/4/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/3d9a2d7e6e24f8689dc2daf77628e857389473da", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/head/CONTRIBUTING.adoc" + } +}, { + "name": "README.md", + "path": "README.md", + "sha": "a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "size": 67, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/4/head", + "html_url": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/head/README.md", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "download_url": "https://raw.githubusercontent.com/cloudbeers/yolo/refs/pull/4/head/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/README.md?ref=refs/pull/4/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/blobs/a39fed575990e3dec86cdbe8dae8a9a7ec0ab3ae", + "html": "https://github.com/cloudbeers/yolo/blob/refs/pull/4/head/README.md" + } +}, { + "name": "foo", + "path": "foo", + "sha": "ed39365664d3b0d96f427b18a893de1883a276e4", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/4/head", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/head/foo", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/foo?ref=refs/pull/4/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/ed39365664d3b0d96f427b18a893de1883a276e4", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/head/foo" + } +}, { + "name": "fu", + "path": "fu", + "sha": "aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "size": 0, + "url": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/4/head", + "html_url": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/head/fu", + "git_url": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "download_url": null, + "type": "dir", + "_links": { + "self": "https://api.github.com/repos/cloudbeers/yolo/contents/fu?ref=refs/pull/4/head", + "git": "https://api.github.com/repos/cloudbeers/yolo/git/trees/aa0eacd6545dd361a18f189d5431f4c5ca455ba3", + "html": "https://github.com/cloudbeers/yolo/tree/refs/pull/4/head/fu" + } +}] diff --git a/src/test/resources/api/mappings/mapping-yolo-contents-B1Oef.json b/src/test/resources/api/mappings/mapping-yolo-contents-B1Oef.json new file mode 100644 index 000000000..c7f9a19c1 --- /dev/null +++ b/src/test/resources/api/mappings/mapping-yolo-contents-B1Oef.json @@ -0,0 +1,34 @@ +{ + "request": { + "url": "/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F3%2Fhead", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "body-yolo-contents-B1Oef.json", + "headers": { + "Server": "GitHub.com", + "Date": "Tue, 06 Dec 2016 17:32:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Status": "200 OK", + "X-RateLimit-Limit": "600", + "X-RateLimit-Remaining": "594", + "X-RateLimit-Reset": "1481048932", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": ["Accept", "Accept-Encoding"], + "ETag": "W/\"7310f2165a6483522e17cf146c65cb07\"", + "Last-Modified": "Wed, 07 Dec 2016 23:55:35 GMT", + "X-GitHub-Media-Type": "github.v3; format=json", + "Access-Control-Expose-Headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", + "Access-Control-Allow-Origin": "*", + "Content-Security-Policy": "default-src 'none'", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "deny", + "X-XSS-Protection": "1; mode=block", + "X-Served-By": "02ea60dfed58b2a09106fafd6ca0c108", + "X-GitHub-Request-Id": "BC8D23FA:31E4:269B4E75:5846F623" + } + } +} diff --git a/src/test/resources/api/mappings/mapping-yolo-contents-GGKvD.json b/src/test/resources/api/mappings/mapping-yolo-contents-GGKvD.json new file mode 100644 index 000000000..7b0e68c14 --- /dev/null +++ b/src/test/resources/api/mappings/mapping-yolo-contents-GGKvD.json @@ -0,0 +1,34 @@ +{ + "request": { + "url": "/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F4%2Fmerge", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "body-yolo-contents-GGKvD.json", + "headers": { + "Server": "GitHub.com", + "Date": "Tue, 06 Dec 2016 17:32:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Status": "200 OK", + "X-RateLimit-Limit": "600", + "X-RateLimit-Remaining": "594", + "X-RateLimit-Reset": "1481048932", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": ["Accept", "Accept-Encoding"], + "ETag": "W/\"7310f2165a6483522e17cf146c65cb07\"", + "Last-Modified": "Wed, 07 Dec 2016 23:55:35 GMT", + "X-GitHub-Media-Type": "github.v3; format=json", + "Access-Control-Expose-Headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", + "Access-Control-Allow-Origin": "*", + "Content-Security-Policy": "default-src 'none'", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "deny", + "X-XSS-Protection": "1; mode=block", + "X-Served-By": "02ea60dfed58b2a09106fafd6ca0c108", + "X-GitHub-Request-Id": "BC8D23FA:31E4:269B4E75:5846F623" + } + } +} diff --git a/src/test/resources/api/mappings/mapping-yolo-contents-cw7Ms.json b/src/test/resources/api/mappings/mapping-yolo-contents-cw7Ms.json new file mode 100644 index 000000000..f08ae7742 --- /dev/null +++ b/src/test/resources/api/mappings/mapping-yolo-contents-cw7Ms.json @@ -0,0 +1,34 @@ +{ + "request": { + "url": "/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F3%2Fmerge", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "body-yolo-contents-cw7Ms.json", + "headers": { + "Server": "GitHub.com", + "Date": "Tue, 06 Dec 2016 17:32:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Status": "200 OK", + "X-RateLimit-Limit": "600", + "X-RateLimit-Remaining": "594", + "X-RateLimit-Reset": "1481048932", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": ["Accept", "Accept-Encoding"], + "ETag": "W/\"0812994aa64fe93cec4891cf5f0554a3\"", + "Last-Modified": "Mon, 21 Nov 2016 22:53:53 GMT", + "X-GitHub-Media-Type": "github.v3; format=json", + "Access-Control-Expose-Headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", + "Access-Control-Allow-Origin": "*", + "Content-Security-Policy": "default-src 'none'", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "deny", + "X-XSS-Protection": "1; mode=block", + "X-Served-By": "02ea60dfed58b2a09106fafd6ca0c108", + "X-GitHub-Request-Id": "BC8D23FA:31E4:269B4E75:5846F623" + } + } +} diff --git a/src/test/resources/api/mappings/mapping-yolo-contents-h9pJz.json b/src/test/resources/api/mappings/mapping-yolo-contents-h9pJz.json new file mode 100644 index 000000000..85d40d637 --- /dev/null +++ b/src/test/resources/api/mappings/mapping-yolo-contents-h9pJz.json @@ -0,0 +1,34 @@ +{ + "request": { + "url": "/repos/cloudbeers/yolo/contents/?ref=refs%2Fpull%2F4%2Fhead", + "method": "GET" + }, + "response": { + "status": 200, + "bodyFileName": "body-yolo-contents-h9pJz.json", + "headers": { + "Server": "GitHub.com", + "Date": "Tue, 06 Dec 2016 17:32:19 GMT", + "Content-Type": "application/json; charset=utf-8", + "Transfer-Encoding": "chunked", + "Status": "200 OK", + "X-RateLimit-Limit": "600", + "X-RateLimit-Remaining": "594", + "X-RateLimit-Reset": "1481048932", + "Cache-Control": "public, max-age=60, s-maxage=60", + "Vary": ["Accept", "Accept-Encoding"], + "ETag": "W/\"0812994aa64fe93cec4891cf5f0554a3\"", + "Last-Modified": "Mon, 21 Nov 2016 22:53:53 GMT", + "X-GitHub-Media-Type": "github.v3; format=json", + "Access-Control-Expose-Headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval", + "Access-Control-Allow-Origin": "*", + "Content-Security-Policy": "default-src 'none'", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "deny", + "X-XSS-Protection": "1; mode=block", + "X-Served-By": "02ea60dfed58b2a09106fafd6ca0c108", + "X-GitHub-Request-Id": "BC8D23FA:31E4:269B4E75:5846F623" + } + } +} From 619978380bf3e6fa95bf22ce86f6f74eef0724ea Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Wed, 28 Jun 2023 15:46:11 +1000 Subject: [PATCH 2/4] [JENKINS-66661] Fix fallback behavior of Fork PR trust criteria --- .../github_branch_source/GitHubSCMSource.java | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index ce2e3cc83..282715b07 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1021,7 +1021,23 @@ protected final void retrieve( request.setPermissionsSource(new GitHubPermissionsSource() { @Override public GHPermissionType fetch(String username) throws IOException, InterruptedException { - return ghRepository.getPermission(username); + try { + return ghRepository.getPermission(username); + } catch (FileNotFoundException e) { + listener.getLogger() + .println(" Not permitted to query list of permissions, assuming none"); + return GHPermissionType.NONE; + } catch (HttpException e) { + if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED + || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN + || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { + listener.getLogger() + .println(" Not permitted to query list of permissions, assuming none"); + return GHPermissionType.NONE; + } else { + throw new WrappedException(e); + } + } } }); @@ -1580,6 +1596,7 @@ private Set updateCollaboratorNames( return collaboratorNames = Collections.emptySet(); } catch (HttpException e) { if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED + || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); return collaboratorNames = Collections.emptySet(); @@ -1866,8 +1883,7 @@ public SCMRevision getTrustedRevision(SCMRevision revision, final TaskListener l try { wrapped.unwrap(); } catch (HttpException e) { - listener.getLogger() - .format("It seems %s is unreachable, assuming no trusted collaborators%n", apiUri); + listener.getLogger().format("It seems %s is unreachable, assuming not trusted%n", apiUri); collaboratorNames = Collections.singleton(repoOwner); } } @@ -2928,7 +2944,21 @@ public GHPermissionType fetch(String username) throws IOException, InterruptedEx String fullName = repoOwner + "/" + repository; repo = github.getRepository(fullName); } - return repo.getPermission(username); + try { + return repo.getPermission(username); + } catch (FileNotFoundException e) { + listener.getLogger().println("Not permitted to query list of permissions, assuming none"); + return GHPermissionType.NONE; + } catch (HttpException e) { + if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED + || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN + || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { + listener.getLogger().println("Not permitted to query list of permissions, assuming none"); + return GHPermissionType.NONE; + } else { + throw new WrappedException(e); + } + } } @Override From 77e6904fa45fe9ecf0dcd125f6c4d6abc5292ecd Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Wed, 5 Jul 2023 21:34:45 +1000 Subject: [PATCH 3/4] [JENKINS-66661] Skip PR if cannot query collaborators / permissions --- .../github_branch_source/GitHubSCMSource.java | 142 +++++++++--------- .../AbstractGitHubWireMockTest.java | 68 ++++++--- .../GitHubSCMSourceTest.java | 35 +---- .../body-cloudbeers-yolo-collaborators.json | 2 +- .../body-yolo-collaborators-ug5Pu.json | 6 +- .../mapping-yolo-collaborators-ug5Pu.json | 6 +- 6 files changed, 128 insertions(+), 131 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index 282715b07..2866aab9d 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1021,23 +1021,7 @@ protected final void retrieve( request.setPermissionsSource(new GitHubPermissionsSource() { @Override public GHPermissionType fetch(String username) throws IOException, InterruptedException { - try { - return ghRepository.getPermission(username); - } catch (FileNotFoundException e) { - listener.getLogger() - .println(" Not permitted to query list of permissions, assuming none"); - return GHPermissionType.NONE; - } catch (HttpException e) { - if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED - || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN - || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { - listener.getLogger() - .println(" Not permitted to query list of permissions, assuming none"); - return GHPermissionType.NONE; - } else { - throw new WrappedException(e); - } - } + return getPermissions(ghRepository, username, listener); } }); @@ -1258,35 +1242,44 @@ private static void retrievePullRequest( } } - if (request.process( - new PullRequestSCMHead(pr, branchName, strategy == ChangeRequestCheckoutStrategy.MERGE), - null, - new SCMSourceRequest.ProbeLambda() { - @NonNull - @Override - public SCMSourceCriteria.Probe create( - @NonNull PullRequestSCMHead head, @Nullable Void revisionInfo) - throws IOException, InterruptedException { - boolean trusted = request.isTrusted(head); - if (!trusted) { - listener.getLogger().format(" (not from a trusted source)%n"); + try { + if (request.process( + new PullRequestSCMHead(pr, branchName, strategy == ChangeRequestCheckoutStrategy.MERGE), + null, + new SCMSourceRequest.ProbeLambda() { + @NonNull + @Override + public SCMSourceCriteria.Probe create( + @NonNull PullRequestSCMHead head, @Nullable Void revisionInfo) + throws IOException, InterruptedException { + boolean trusted = request.isTrusted(head); + if (!trusted) { + listener.getLogger().format(" (not from a trusted source)%n"); + } + return new GitHubSCMProbe( + apiUri, credentials, ghRepository, trusted ? head : head.getTarget(), null); } - return new GitHubSCMProbe( - apiUri, credentials, ghRepository, trusted ? head : head.getTarget(), null); - } - }, - new SCMSourceRequest.LazyRevisionLambda() { - @NonNull - @Override - public SCMRevision create(@NonNull PullRequestSCMHead head, @Nullable Void ignored) - throws IOException, InterruptedException { + }, + new SCMSourceRequest.LazyRevisionLambda() { + @NonNull + @Override + public SCMRevision create(@NonNull PullRequestSCMHead head, @Nullable Void ignored) + throws IOException, InterruptedException { - return createPullRequestSCMRevision(pr, head, listener, ghRepository); - } - }, - new MergabilityWitness(pr, strategy, listener), - new CriteriaWitness(listener))) { - listener.getLogger().format("%n Pull request %d processed (query completed)%n", number); + return createPullRequestSCMRevision(pr, head, listener, ghRepository); + } + }, + new MergabilityWitness(pr, strategy, listener), + new CriteriaWitness(listener))) { + listener.getLogger().format("%n Pull request %d processed (query completed)%n", number); + } + } catch (WrappedException e) { + try { + e.unwrap(); + } catch (Exception ue) { + LOGGER.log(Level.FINE, "Failed to process pull request", ue); + } + listener.getLogger().format("%n Failed to process pull request %d, skipping%n%n", number); } } } @@ -1583,27 +1576,52 @@ private Set updateCollaboratorNames( @NonNull GHRepository ghRepository) throws IOException { if (credentials == null && (apiUri == null || GITHUB_URL.equals(apiUri))) { - // anonymous access to GitHub will never get list of collaborators and will - // burn an API call, so no point in even trying - listener.getLogger().println("Anonymous cannot query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); + listener.getLogger().println(" Anonymous cannot query list of collaborators"); + throw new IOException("Anonymous cannot query list of collaborators"); } else { try { return collaboratorNames = new HashSet<>(ghRepository.getCollaboratorNames()); } catch (FileNotFoundException e) { - // not permitted - listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); + // listener.getLogger().println(" Not permitted to query list of collaborators"); + listener.getLogger().format(" Not permitted to query list of collaborators: %s", e.getMessage()); + throw e; } catch (HttpException e) { if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { - listener.getLogger().println("Not permitted to query list of collaborators, assuming none"); - return collaboratorNames = Collections.emptySet(); + // listener.getLogger().println(" Not permitted to query list of + // collaborators"); + listener.getLogger() + .format(" Not permitted to query list of collaborators: %s", e.getMessage()); } else { - throw e; + // listener.getLogger().println(" Failed to query list of collaborators"); + listener.getLogger().format(" Failed to query list of collaborators: %s", e.getMessage()); } + throw e; + } + } + } + + private GHPermissionType getPermissions( + @NonNull GHRepository repo, @NonNull String username, @NonNull TaskListener listener) throws IOException { + + try { + return repo.getPermission(username); + } catch (FileNotFoundException e) { + // listener.getLogger().println(" Not permitted to query list of permissions"); + listener.getLogger().format(" Not permitted to query list of permissions: %s", e.getMessage()); + throw new WrappedException(e); + } catch (HttpException e) { + if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED + || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN + || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { + // listener.getLogger().println(" Not permitted to query list of permissions"); + listener.getLogger().format(" Not permitted to query list of permissions: %s", e.getMessage()); + } else { + // listener.getLogger().println(" Failed to query list of permissions"); + listener.getLogger().format(" Failed to query list of permissions: %s", e.getMessage()); } + throw new WrappedException(e); } } @@ -2944,21 +2962,7 @@ public GHPermissionType fetch(String username) throws IOException, InterruptedEx String fullName = repoOwner + "/" + repository; repo = github.getRepository(fullName); } - try { - return repo.getPermission(username); - } catch (FileNotFoundException e) { - listener.getLogger().println("Not permitted to query list of permissions, assuming none"); - return GHPermissionType.NONE; - } catch (HttpException e) { - if (e.getResponseCode() == HttpServletResponse.SC_UNAUTHORIZED - || e.getResponseCode() == HttpServletResponse.SC_FORBIDDEN - || e.getResponseCode() == HttpServletResponse.SC_NOT_FOUND) { - listener.getLogger().println("Not permitted to query list of permissions, assuming none"); - return GHPermissionType.NONE; - } else { - throw new WrappedException(e); - } - } + return getPermissions(repo, username, listener); } @Override diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java index f4636fa52..e88498292 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/AbstractGitHubWireMockTest.java @@ -3,11 +3,14 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.ResponseTransformer; +import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterAction; +import com.github.tomakehurst.wiremock.extension.requestfilter.StubRequestFilter; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -38,30 +41,47 @@ public abstract class AbstractGitHubWireMockTest { @Rule public WireMockRule githubApi = factory.getRule(WireMockConfiguration.options() .dynamicPort() + .needClientAuth(true) .usingFilesUnderClasspath("api") - .extensions(new ResponseTransformer() { - @Override - public Response transform(Request request, Response response, FileSource files, Parameters parameters) { - if ("application/json" - .equals(response.getHeaders().getContentTypeHeader().mimeTypePart())) { - return Response.Builder.like(response) - .but() - .body(response.getBodyAsString() - .replace( - "https://api.github.com/", "http://localhost:" + githubApi.port() + "/") - .replace( - "https://raw.githubusercontent.com/", - "http://localhost:" + githubRaw.port() + "/")) - .build(); - } - return response; - } + .extensions( + new ResponseTransformer() { + @Override + public Response transform( + Request request, Response response, FileSource files, Parameters parameters) { + if ("application/json" + .equals(response.getHeaders() + .getContentTypeHeader() + .mimeTypePart())) { + return Response.Builder.like(response) + .but() + .body(response.getBodyAsString() + .replace( + "https://api.github.com/", + "http://localhost:" + githubApi.port() + "/") + .replace( + "https://raw.githubusercontent.com/", + "http://localhost:" + githubRaw.port() + "/")) + .build(); + } + return response; + } - @Override - public String getName() { - return "url-rewrite"; - } - })); + @Override + public String getName() { + return "url-rewrite"; + } + }, + new StubRequestFilter() { + @Override + public RequestFilterAction filter(Request request) { + return RequestFilterAction.continueWith(request); + } + + @Override + public String getName() { + return "test-auth"; + } + })); @Before public void prepareMockGitHub() { @@ -89,4 +109,8 @@ void prepareMockGitHubFileMappings() { new SingleRootFileSource("src/test/resources/raw/mappings"), new SingleRootFileSource("src/test/resources/raw/__files")); } + + StandardUsernameCredentials getCredentials() { + return null; + } } diff --git a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java index 214a17e43..b2cf50aff 100644 --- a/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java +++ b/src/test/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSourceTest.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasProperty; @@ -774,22 +775,7 @@ public void fetchForkPRCannotFindCollaborators() throws IOException, Interrupted null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h : collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); - - // Cannot find collaborators, the trusted revision must be the base (target) revision, not the head - for (SCMRevision revision : revByName.values()) { - assertThat( - source.getTrustedRevision( - revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), - is(((PullRequestSCMRevision) revision).getTarget())); - } + assertThat(collector.result().entrySet(), is(empty())); } } @@ -884,22 +870,7 @@ public void fetchForkPRCannotFindPermissions() throws IOException, InterruptedEx null, null); - Map byName = new HashMap<>(); - Map revByName = new HashMap<>(); - for (Map.Entry h : collector.result().entrySet()) { - byName.put(h.getKey().getName(), h.getKey()); - revByName.put(h.getKey().getName(), h.getValue()); - } - - assertThat(byName.keySet(), containsInAnyOrder("PR-2", "PR-3", "PR-4")); - - // Cannot find collaborators, the trusted revision must be the base (target) revision, not the head - for (SCMRevision revision : revByName.values()) { - assertThat( - source.getTrustedRevision( - revision, new LogTaskListener(Logger.getAnonymousLogger(), Level.INFO)), - is(((PullRequestSCMRevision) revision).getTarget())); - } + assertThat(collector.result().entrySet(), is(empty())); } } diff --git a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json index c540cfc28..6e0b37f28 100644 --- a/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json +++ b/src/test/resources/api/__files/body-cloudbeers-yolo-collaborators.json @@ -25,7 +25,7 @@ "triage": true, "pull": true }, - "role_name": "read" + "role_name": "write" }, { "login": "cloudbeers", diff --git a/src/test/resources/api/__files/body-yolo-collaborators-ug5Pu.json b/src/test/resources/api/__files/body-yolo-collaborators-ug5Pu.json index bf48f8dbd..0d4f101c7 100644 --- a/src/test/resources/api/__files/body-yolo-collaborators-ug5Pu.json +++ b/src/test/resources/api/__files/body-yolo-collaborators-ug5Pu.json @@ -1,4 +1,2 @@ -{ - "message": "Requires authentication", - "documentation_url": "https://developer.github.com/v3" -} \ No newline at end of file +[ +] diff --git a/src/test/resources/api/mappings/mapping-yolo-collaborators-ug5Pu.json b/src/test/resources/api/mappings/mapping-yolo-collaborators-ug5Pu.json index b2b0f9f29..4a07e7604 100644 --- a/src/test/resources/api/mappings/mapping-yolo-collaborators-ug5Pu.json +++ b/src/test/resources/api/mappings/mapping-yolo-collaborators-ug5Pu.json @@ -4,13 +4,13 @@ "method": "GET" }, "response": { - "status": 401, + "status": 200, "bodyFileName": "body-yolo-collaborators-ug5Pu.json", "headers": { "Server": "GitHub.com", "Date": "Tue, 06 Dec 2016 17:32:19 GMT", "Content-Type": "application/json; charset=utf-8", - "Status": "401 Unauthorized", + "Status": "200 OK", "X-RateLimit-Limit": "600", "X-RateLimit-Remaining": "596", "X-RateLimit-Reset": "1481048932", @@ -25,4 +25,4 @@ "X-GitHub-Request-Id": "BC8D23FA:31E4:269B4DEE:5846F622" } } -} \ No newline at end of file +} From 6237240cf2bdbc267bf73145747d9cb9f23e2617 Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Wed, 5 Jul 2023 21:35:31 +1000 Subject: [PATCH 4/4] [JENKINS-66661] Fail Trust revision retrieval if cannot query collaborators / permissions --- .../plugins/github_branch_source/GitHubSCMSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java index 2866aab9d..3e82e2db4 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMSource.java @@ -1901,8 +1901,8 @@ public SCMRevision getTrustedRevision(SCMRevision revision, final TaskListener l try { wrapped.unwrap(); } catch (HttpException e) { - listener.getLogger().format("It seems %s is unreachable, assuming not trusted%n", apiUri); - collaboratorNames = Collections.singleton(repoOwner); + listener.getLogger().format("Cannot retrieve trusted revision: %s%n"); + throw e; } } PullRequestSCMRevision rev = (PullRequestSCMRevision) revision;