Skip to content

Commit

Permalink
Merge pull request #527 from jglick/Credentials.forRun
Browse files Browse the repository at this point in the history
[JENKINS-62220] Automatically select `owner` for `GitHubAppCredentials` acc. to context
  • Loading branch information
jtnord committed May 6, 2022
2 parents 962f131 + 8458112 commit b2f5129
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 49 deletions.
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.29</version>
<version>4.38</version>
<relativePath />
</parent>
<artifactId>github-branch-source</artifactId>
Expand Down Expand Up @@ -48,6 +48,11 @@
<artifactId>github</artifactId>
<version>1.34.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1087.v16065d268466</version> <!-- TODO until in BOM -->
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public static ListBoxModel listScanCredentials(@CheckForNull Item context, Strin
* @param apiUri the api endpoint.
* @param scanCredentialsId the credentials ID.
* @return the {@link FormValidation} results.
* @deprecated use {@link #checkScanCredentials(Item, String, String)}
* @deprecated use {@link #checkScanCredentials(Item, String, String, String)}
*/
@Deprecated
public static FormValidation checkScanCredentials(
Expand All @@ -168,9 +168,29 @@ public static FormValidation checkScanCredentials(
* @param apiUri the api endpoint.
* @param scanCredentialsId the credentials ID.
* @return the {@link FormValidation} results.
* @deprecated use {@link #checkScanCredentials(Item, String, String, String)}
*/
@Deprecated
public static FormValidation checkScanCredentials(
@CheckForNull Item context, String apiUri, String scanCredentialsId) {
return checkScanCredentials(context, apiUri, scanCredentialsId, null);
}

/**
* Checks the credential ID for use as scan credentials in the supplied context against the
* supplied API endpoint.
*
* @param context the context.
* @param apiUri the api endpoint.
* @param scanCredentialsId the credentials ID.
* @param repoOwner the org/user
* @return the {@link FormValidation} results.
*/
public static FormValidation checkScanCredentials(
@CheckForNull Item context,
String apiUri,
String scanCredentialsId,
@CheckForNull String repoOwner) {
if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)
|| context != null && !context.hasPermission(Item.EXTENDED_READ)) {
return FormValidation.ok();
Expand All @@ -194,7 +214,8 @@ public static FormValidation checkScanCredentials(
Connector.lookupScanCredentials(
context,
StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL),
scanCredentialsId);
scanCredentialsId,
repoOwner);
if (credentials == null) {
return FormValidation.error("Credentials not found");
} else {
Expand Down Expand Up @@ -241,7 +262,7 @@ public static FormValidation checkScanCredentials(
* @param apiUri the API endpoint.
* @param scanCredentialsId the credentials to resolve.
* @return the {@link StandardCredentials} or {@code null}
* @deprecated use {@link #lookupScanCredentials(Item, String, String)}
* @deprecated use {@link #lookupScanCredentials(Item, String, String, String)}
*/
@Deprecated
@CheckForNull
Expand All @@ -260,25 +281,52 @@ public static StandardCredentials lookupScanCredentials(
* @param apiUri the API endpoint.
* @param scanCredentialsId the credentials to resolve.
* @return the {@link StandardCredentials} or {@code null}
* @deprecated use {@link #lookupScanCredentials(Item, String, String, String)}
*/
@Deprecated
@CheckForNull
public static StandardCredentials lookupScanCredentials(
@CheckForNull Item context,
@CheckForNull String apiUri,
@CheckForNull String scanCredentialsId) {
return lookupScanCredentials(context, apiUri, scanCredentialsId, null);
}

/**
* Resolves the specified scan credentials in the specified context for use against the specified
* API endpoint.
*
* @param context the context.
* @param apiUri the API endpoint.
* @param scanCredentialsId the credentials to resolve.
* @param repoOwner the org/user
* @return the {@link StandardCredentials} or {@code null}
*/
@CheckForNull
public static StandardCredentials lookupScanCredentials(
@CheckForNull Item context,
@CheckForNull String apiUri,
@CheckForNull String scanCredentialsId,
@CheckForNull String repoOwner) {
if (Util.fixEmpty(scanCredentialsId) == null) {
return null;
} else {
return CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class,
context,
context instanceof Queue.Task
? ((Queue.Task) context).getDefaultAuthentication()
: ACL.SYSTEM,
githubDomainRequirements(apiUri)),
CredentialsMatchers.allOf(
CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()));
StandardCredentials c =
CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class,
context,
context instanceof Queue.Task
? ((Queue.Task) context).getDefaultAuthentication()
: ACL.SYSTEM,
githubDomainRequirements(apiUri)),
CredentialsMatchers.allOf(
CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()));
if (c instanceof GitHubAppCredentials && repoOwner != null) {
return ((GitHubAppCredentials) c).withOwner(repoOwner);
} else {
return c;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

import static org.jenkinsci.plugins.github_branch_source.GitHubSCMNavigator.DescriptorImpl.getPossibleApiUriItems;

import com.cloudbees.jenkins.GitHubRepositoryName;
import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
import com.coravy.hudson.plugins.github.GithubProjectProperty;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.Functions;
import hudson.Util;
import hudson.model.Job;
import hudson.model.Run;
import hudson.remoting.Channel;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
Expand All @@ -20,10 +25,13 @@
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.scm.api.SCMSource;
import jenkins.security.SlaveToMasterCallable;
import jenkins.util.JenkinsJVM;
import net.sf.json.JSONObject;
Expand Down Expand Up @@ -73,10 +81,18 @@ public class GitHubAppCredentials extends BaseStandardCredentials

private String apiUri;

@SuppressFBWarnings(
value = "IS2_INCONSISTENT_SYNC",
justification = "#withOwner locking only for #byOwner")
private String owner;

private transient AppInstallationToken cachedToken;

/**
* Cache of credentials specialized by {@link #owner}, so that {@link #cachedToken} is preserved.
*/
private transient Map<String, GitHubAppCredentials> byOwner;

@DataBoundConstructor
@SuppressWarnings("unused") // by stapler
public GitHubAppCredentials(
Expand Down Expand Up @@ -310,6 +326,47 @@ public String getUsername() {
return appID;
}

@NonNull
public synchronized GitHubAppCredentials withOwner(@NonNull String owner) {
if (this.owner != null) {
if (!owner.equals(this.owner)) {
throw new IllegalArgumentException("Owner mismatch: " + this.owner + " vs. " + owner);
}
return this;
}
if (byOwner == null) {
byOwner = new HashMap<>();
}
return byOwner.computeIfAbsent(
owner,
k -> {
GitHubAppCredentials clone =
new GitHubAppCredentials(getScope(), getId(), getDescription(), appID, privateKey);
clone.apiUri = apiUri;
clone.owner = owner;
return clone;
});
}

@NonNull
@Override
public Credentials forRun(Run<?, ?> context) {
if (owner != null) {
return this;
}
Job<?, ?> job = context.getParent();
SCMSource src = SCMSource.SourceByItem.findSource(job);
if (src instanceof GitHubSCMSource) {
return withOwner(((GitHubSCMSource) src).getRepoOwner());
}
GitHubRepositoryName ghrn =
GitHubRepositoryName.create(job.getProperty(GithubProjectProperty.class));
if (ghrn != null) {
return withOwner(ghrn.userName);
}
return this;
}

private AppInstallationToken getCachedToken() {
synchronized (this) {
return cachedToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ private static GitHub lookUpGitHub(@NonNull Job<?, ?> job) throws IOException {
return Connector.connect(
source.getApiUri(),
Connector.lookupScanCredentials(
job, source.getApiUri(), source.getScanCredentialsId()));
job, source.getApiUri(), source.getScanCredentialsId(), source.getRepoOwner()));
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public SCMFileSystem build(
String apiUri = src.getApiUri();
StandardCredentials credentials =
Connector.lookupScanCredentials(
(Item) src.getOwner(), apiUri, src.getScanCredentialsId());
(Item) src.getOwner(), apiUri, src.getScanCredentialsId(), src.getRepoOwner());

// Github client and validation
GitHub github = Connector.connect(apiUri, credentials);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,8 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
}

StandardCredentials credentials =
Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId);
Connector.lookupScanCredentials(
(Item) observer.getContext(), apiUri, credentialsId, repoOwner);

// Github client and validation
GitHub github = Connector.connect(apiUri, credentials);
Expand Down Expand Up @@ -1262,7 +1263,8 @@ public void visitSource(String sourceName, SCMSourceObserver observer)
}

StandardCredentials credentials =
Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId);
Connector.lookupScanCredentials(
(Item) observer.getContext(), apiUri, credentialsId, repoOwner);

// Github client and validation
GitHub github;
Expand Down Expand Up @@ -1577,8 +1579,8 @@ public List<Action> retrieveActions(
List<Action> result = new ArrayList<>();
String apiUri = Util.fixEmptyAndTrim(getApiUri());
StandardCredentials credentials =
Connector.lookupScanCredentials((Item) owner, apiUri, credentialsId);
GitHub hub = Connector.connect(apiUri, credentials);
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner);
GitHub hub = Connector.connect(getApiUri(), credentials);
boolean privateMode = determinePrivateMode(apiUri);
try {
Connector.configureLocalRateLimitChecker(listener, hub);
Expand Down Expand Up @@ -1631,7 +1633,7 @@ public void afterSave(@NonNull SCMNavigatorOwner owner) {
try {
// FIXME MINOR HACK ALERT
StandardCredentials credentials =
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId);
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner);
GitHub hub = Connector.connect(getApiUri(), credentials);
try {
GitHubOrgWebHook.register(hub, repoOwner);
Expand Down Expand Up @@ -1757,8 +1759,9 @@ protected SCMSourceCategory[] createCategories() {
public FormValidation doCheckCredentialsId(
@CheckForNull @AncestorInPath Item context,
@QueryParameter String apiUri,
@QueryParameter String credentialsId) {
return Connector.checkScanCredentials(context, apiUri, credentialsId);
@QueryParameter String credentialsId,
@QueryParameter String repoOwner) {
return Connector.checkScanCredentials(context, apiUri, credentialsId, repoOwner);
}

/**
Expand Down
Loading

0 comments on commit b2f5129

Please sign in to comment.