Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-73474] Add GitHubAppCredentials.withOwner non regression test #804

Merged
merged 11 commits into from
Oct 8, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.jenkinsci.plugins.github_branch_source;

import com.cloudbees.plugins.credentials.CredentialsScope;
import hudson.util.Secret;

public class GitHubApp {

// PKCS8 private key (https://stackoverflow.com/a/22176759/4951015)
private static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n"
+
Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW when updating to 2.479.x we can switch to a multiline text block which would be more legible.

// Windows line ending
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n"
+
// This should also work
"5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r"
+ "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n"
+ "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n"
+ "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n"
+ "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n"
+ "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n"
+ "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n"
+ "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n"
+ "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n"
+ "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n"
+ "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n"
+ "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n"
+ "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n"
+ "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n"
+ "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n"
+ "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n"
+ "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n"
+ "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n"
+ "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n"
+ "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n"
+ "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n"
+ "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n"
+ "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n"
+ "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n"
+ "Nw9bewRvqjySBlDJ9/aNSeEY\n"
+ "-----END PRIVATE KEY-----";

public static GitHubAppCredentials createCredentials(final String id) {
return new GitHubAppCredentials(CredentialsScope.GLOBAL, id, "sample", "54321", Secret.fromString(PRIVATE_KEY));
}

public static GitHubAppCredentials createCredentials(final String id, final String owner) {
final var credentials = createCredentials(id);
credentials.setOwner(owner);
return credentials;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.jenkinsci.plugins.github_branch_source;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.domains.Domain;
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import jenkins.branch.BranchSource;
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
import org.junit.Before;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;

@Issue("JENKINS-73474")
public class GitHubAppCredentialsContextualizationTest extends AbstractGitHubWireMockTest {

@Before
public void setUpWireMock() throws Exception {
GitHubConfiguration.get().setApiRateLimitChecker(ApiRateLimitChecker.ThrottleOnOver);

// Add wiremock responses for App, App Installation, and App Installation Token
githubApi.stubFor(get(urlEqualTo("/app"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../AppCredentials/files/body-mapping-githubapp-app.json")));
githubApi.stubFor(get(urlEqualTo("/app/installations"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../AppCredentials/files/body-mapping-githubapp-installations.json")));
githubApi.stubFor(post(urlEqualTo("/app/installations/654321/access_tokens"))
.withRequestBody(equalToJson(
"{\"permissions\":{\"pull_requests\":\"write\",\"metadata\":\"read\",\"checks\":\"write\",\"contents\":\"read\"}}",
true,
false))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBody("{\"token\":\"super-secret-token\",\"expires_at\":\"" + createTokenExpiration()
+ "\"}")));

// Add wiremock responses for Repository
githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo.json")));
githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/branches"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-branches.json")));
githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/contents/?ref=refs%2Fheads%2Fmaster"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-contents.json")));
githubApi.stubFor(get(urlEqualTo("/repos/cloudbeers/multibranch-demo/pulls?state=open"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json; charset=utf-8")
.withBodyFile("../contextualization/body-repos-cloudbeers-multibranch-demo-pulls.json")));
}

@Test
jeromepochat marked this conversation as resolved.
Show resolved Hide resolved
public void ownerMustBeInferredFromRepository() throws Exception {
final var store = CredentialsProvider.lookupStores(r.jenkins).iterator().next();

final var credentials = GitHubApp.createCredentials("myAppCredentialsWithoutOwner");
store.addCredentials(Domain.global(), credentials);
credentials.setApiUri(githubApi.baseUrl());

final var scmSource = new GitHubSCMSource("cloudbeers", "multibranch-demo", null, false);
scmSource.setTraits(Arrays.asList(new BranchDiscoveryTrait(true, true)));
scmSource.setCredentialsId(credentials.getId());
scmSource.setApiUri(githubApi.baseUrl());

final var multiBranchProject = r.jenkins.createProject(WorkflowMultiBranchProject.class, "multibranch-demo");
multiBranchProject.getSourcesList().add(new BranchSource(scmSource));
multiBranchProject.scheduleBuild2(0).getFuture().get();

final var branchProject = multiBranchProject.getItem("master");
assertThat(branchProject, notNullValue());
}

private String createTokenExpiration() {
// This token will go stale at the soonest allowed time but will not
// expire for the duration of the test
// Format: 2019-08-10T05:54:58Z
return DateTimeFormatter.ISO_INSTANT.format(
Instant.now().plus(Duration.ofMinutes(10)).truncatedTo(ChronoUnit.SECONDS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.equalTo;
import static org.jenkinsci.plugins.github_branch_source.Connector.createGitHubBuilder;
import static org.junit.Assert.assertThrows;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.github.tomakehurst.wiremock.http.RequestMethod;
Expand All @@ -19,7 +19,6 @@
import hudson.model.Result;
import hudson.model.Slave;
import hudson.model.StringParameterDefinition;
import hudson.util.Secret;
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
Expand Down Expand Up @@ -56,40 +55,6 @@ public class GithubAppCredentialsTest extends AbstractGitHubWireMockTest {
private static GitHubAppCredentials appCredentials, appCredentialsNoOwner;
private static LogRecorder logRecorder;

// https://stackoverflow.com/a/22176759/4951015
public static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n"
Copy link
Contributor Author

@jeromepochat jeromepochat Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to GitHubApp for reuse from multiple test classes.

+
// Windows line ending
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD7vHsVwyDV8cj7\r\n"
+
// This should also work
"5yR4WWl6rlgf/e5zmeBgtm0PCgnitcSbD5FU33301DPY5a7AtqVBOwEnE14L9XS7\r"
+ "ov61U+x1m4aQmqR/dPQaA2ayh2cYPszWNQMp42ArDIfg7DhSrvsRJKHsbPXlPjqe\n"
+ "c0udLqhSLVIO9frNLf+dAsLsgYk8O39PKGb33akGG7tWTe0J+akNQjgbS7vOi8sS\n"
+ "NLwHIdYfz/Am+6Xmm+J4yVs6+Xt3kOeLdFBkz8H/HGsJq854MbIAK/HuId1MOPS0\n"
+ "cDWh37tzRsM+q/HZzYRkc5bhNKw/Mj9jN9jD5GH0Lfea0QFedjppf1KvWdcXn+/W\n"
+ "M7OmyfhvAgMBAAECggEAN96H7reExRbJRWbySCeH6mthMZB46H0hODWklK7krMUs\n"
+ "okFdPtnvKXQjIaMwGqMuoACJa/O3bq4GP1KYdwPuOdfPkK5RjdwWBOP2We8FKXNe\n"
+ "oLfZQOWuxT8dtQSYJ3mgTRi1OzSfikY6Wko6YOMnBj36tUlQZVMtJNqlCjphi9Uz\n"
+ "6EyvRURlDG8sBBbC7ods5B0789qk3iGH/97ia+1QIqXAUaVFg3/BA6wkxkbNG2sN\n"
+ "tqULgVYTw32Oj/Y/H1Y250RoocTyfsUS3I3aPIlnvcgp2bugWqDyYJ58nDIt3Pku\n"
+ "fjImWrNz/pNiEs+efnb0QEk7m5hYwxmyXN4KRSv0OQKBgQD+I3Y3iNKSVr6wXjur\n"
+ "OPp45fxS2sEf5FyFYOn3u760sdJOH9fGlmf9sDozJ8Y8KCaQCN5tSe3OM+XDrmiw\n"
+ "Cu/oaqJ1+G4RG+6w1RJF+5Nfg6PkUs7eJehUgZ2Tox8Tg1mfVIV8KbMwNi5tXpug\n"
+ "MVmA2k9xjc4uMd2jSnSj9NAqrQKBgQD9lIO1tY6YKF0Eb0Qi/iLN4UqBdJfnALBR\n"
+ "MjxYxqqI8G4wZEoZEJJvT1Lm6Q3o577N95SihZoj69tb10vvbEz1pb3df7c1HEku\n"
+ "LXcyVMvjR/CZ7dOSNgLGAkFfOoPhcF/OjSm4DrGPe3GiBxhwXTBjwJ5TIgEDkVIx\n"
+ "ZVo5r7gPCwKBgQCOvsZo/Q4hql2jXNqxGuj9PVkUBNFTI4agWEYyox7ECdlxjks5\n"
+ "vUOd5/1YvG+JXJgEcSbWRh8volDdL7qXnx0P881a6/aO35ybcKK58kvd62gEGEsf\n"
+ "1jUAOmmTAp2y7SVK7EOp8RY370b2oZxSR0XZrUXQJ3F22wV98ZVAfoLqZQKBgDIr\n"
+ "PdunbezAn5aPBOX/bZdZ6UmvbZYwVrHZxIKz2214U/STAu3uj2oiQX6ZwTzBDMjn\n"
+ "IKr+z74nnaCP+eAGhztabTPzXqXNUNUn/Zshl60BwKJToTYeJXJTY+eZRhpGB05w\n"
+ "Mz7M+Wgvvg2WZcllRnuV0j0UTysLhz1qle0vzLR9AoGBAOukkFFm2RLm9N1P3gI8\n"
+ "mUadeAlYRZ5o0MvumOHaB5pDOCKhrqAhop2gnM0f5uSlapCtlhj0Js7ZyS3Giezg\n"
+ "38oqAhAYxy2LMoLD7UtsHXNp0OnZ22djcDwh+Wp2YORm7h71yOM0NsYubGbp+CmT\n"
+ "Nw9bewRvqjySBlDJ9/aNSeEY\n"
+ "-----END PRIVATE KEY-----";

@Rule
public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();

Expand All @@ -106,16 +71,9 @@ public static void setUpJenkins() throws Exception {
// Add credential (Must have valid private key for Jwt to work, but App doesn't have to actually
// exist)
store = CredentialsProvider.lookupStores(r.jenkins).iterator().next();
appCredentials = new GitHubAppCredentials(
CredentialsScope.GLOBAL, myAppCredentialsId, "sample", "54321", Secret.fromString(PKCS8_PRIVATE_KEY));
appCredentials.setOwner("cloudBeers");
appCredentials = GitHubApp.createCredentials(myAppCredentialsId, "cloudBeers");
store.addCredentials(Domain.global(), appCredentials);
appCredentialsNoOwner = new GitHubAppCredentials(
CredentialsScope.GLOBAL,
myAppCredentialsNoOwnerId,
"sample",
"54321",
Secret.fromString(PKCS8_PRIVATE_KEY));
appCredentialsNoOwner = GitHubApp.createCredentials(myAppCredentialsNoOwnerId);
store.addCredentials(Domain.global(), appCredentialsNoOwner);

// Add agent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"name": "master",
"commit": {
"sha": "ba1fdaa6c2088cecea85333e763710cd3f39688f",
"url": "https://api.github.com/repos/cloudbeers/multibranch-demo/commits/ba1fdaa6c2088cecea85333e763710cd3f39688f"
},
"protected": false
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[
{
"name": "Jenkinsfile",
"path": "Jenkinsfile",
"sha": "98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba",
"size": 351,
"url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/Jenkinsfile?ref=refs/heads/master",
"html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/Jenkinsfile",
"git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba",
"download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/Jenkinsfile",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/Jenkinsfile?ref=refs/heads/master",
"git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/98c0f1dad6a7874ff6a7ccc4dd72a5519ec8fdba",
"html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/Jenkinsfile"
}
},
{
"name": "README.md",
"path": "README.md",
"sha": "59e16b0d408539ab4f23d64f2cafd33495eb21ad",
"size": 131,
"url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/README.md?ref=refs/heads/master",
"html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/README.md",
"git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/59e16b0d408539ab4f23d64f2cafd33495eb21ad",
"download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/README.md",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/README.md?ref=refs/heads/master",
"git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/59e16b0d408539ab4f23d64f2cafd33495eb21ad",
"html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/README.md"
}
},
{
"name": "hello-world.go",
"path": "hello-world.go",
"sha": "b64817ed596a1dae19dfa3f8a6e6bc26506cc645",
"size": 75,
"url": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/hello-world.go?ref=refs/heads/master",
"html_url": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/hello-world.go",
"git_url": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/b64817ed596a1dae19dfa3f8a6e6bc26506cc645",
"download_url": "https://raw.githubusercontent.com/cloudbeers/multibranch-demo/refs/heads/master/hello-world.go",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/cloudbeers/multibranch-demo/contents/hello-world.go?ref=refs/heads/master",
"git": "https://api.github.com/repos/cloudbeers/multibranch-demo/git/blobs/b64817ed596a1dae19dfa3f8a6e6bc26506cc645",
"html": "https://github.com/cloudbeers/multibranch-demo/blob/refs/heads/master/hello-world.go"
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading
Loading