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-62220] Automatically select owner for GitHubAppCredentials acc. to context #527

Merged
merged 8 commits into from
May 6, 2022
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
Copy link
Member Author

Choose a reason for hiding this comment

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

Or could comment this out while reverting POM changes.

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