Skip to content

Commit b2f5129

Browse files
authored
Merge pull request #527 from jglick/Credentials.forRun
[JENKINS-62220] Automatically select `owner` for `GitHubAppCredentials` acc. to context
2 parents 962f131 + 8458112 commit b2f5129

File tree

10 files changed

+196
-49
lines changed

10 files changed

+196
-49
lines changed

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>org.jenkins-ci.plugins</groupId>
66
<artifactId>plugin</artifactId>
7-
<version>4.29</version>
7+
<version>4.38</version>
88
<relativePath />
99
</parent>
1010
<artifactId>github-branch-source</artifactId>
@@ -48,6 +48,11 @@
4848
<artifactId>github</artifactId>
4949
<version>1.34.3</version>
5050
</dependency>
51+
<dependency>
52+
<groupId>org.jenkins-ci.plugins</groupId>
53+
<artifactId>credentials</artifactId>
54+
<version>1087.v16065d268466</version> <!-- TODO until in BOM -->
55+
</dependency>
5156
<dependency>
5257
<groupId>org.jenkins-ci.plugins.workflow</groupId>
5358
<artifactId>workflow-support</artifactId>

src/main/java/org/jenkinsci/plugins/github_branch_source/Connector.java

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public static ListBoxModel listScanCredentials(@CheckForNull Item context, Strin
152152
* @param apiUri the api endpoint.
153153
* @param scanCredentialsId the credentials ID.
154154
* @return the {@link FormValidation} results.
155-
* @deprecated use {@link #checkScanCredentials(Item, String, String)}
155+
* @deprecated use {@link #checkScanCredentials(Item, String, String, String)}
156156
*/
157157
@Deprecated
158158
public static FormValidation checkScanCredentials(
@@ -168,9 +168,29 @@ public static FormValidation checkScanCredentials(
168168
* @param apiUri the api endpoint.
169169
* @param scanCredentialsId the credentials ID.
170170
* @return the {@link FormValidation} results.
171+
* @deprecated use {@link #checkScanCredentials(Item, String, String, String)}
171172
*/
173+
@Deprecated
172174
public static FormValidation checkScanCredentials(
173175
@CheckForNull Item context, String apiUri, String scanCredentialsId) {
176+
return checkScanCredentials(context, apiUri, scanCredentialsId, null);
177+
}
178+
179+
/**
180+
* Checks the credential ID for use as scan credentials in the supplied context against the
181+
* supplied API endpoint.
182+
*
183+
* @param context the context.
184+
* @param apiUri the api endpoint.
185+
* @param scanCredentialsId the credentials ID.
186+
* @param repoOwner the org/user
187+
* @return the {@link FormValidation} results.
188+
*/
189+
public static FormValidation checkScanCredentials(
190+
@CheckForNull Item context,
191+
String apiUri,
192+
String scanCredentialsId,
193+
@CheckForNull String repoOwner) {
174194
if (context == null && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)
175195
|| context != null && !context.hasPermission(Item.EXTENDED_READ)) {
176196
return FormValidation.ok();
@@ -194,7 +214,8 @@ public static FormValidation checkScanCredentials(
194214
Connector.lookupScanCredentials(
195215
context,
196216
StringUtils.defaultIfEmpty(apiUri, GitHubServerConfig.GITHUB_URL),
197-
scanCredentialsId);
217+
scanCredentialsId,
218+
repoOwner);
198219
if (credentials == null) {
199220
return FormValidation.error("Credentials not found");
200221
} else {
@@ -241,7 +262,7 @@ public static FormValidation checkScanCredentials(
241262
* @param apiUri the API endpoint.
242263
* @param scanCredentialsId the credentials to resolve.
243264
* @return the {@link StandardCredentials} or {@code null}
244-
* @deprecated use {@link #lookupScanCredentials(Item, String, String)}
265+
* @deprecated use {@link #lookupScanCredentials(Item, String, String, String)}
245266
*/
246267
@Deprecated
247268
@CheckForNull
@@ -260,25 +281,52 @@ public static StandardCredentials lookupScanCredentials(
260281
* @param apiUri the API endpoint.
261282
* @param scanCredentialsId the credentials to resolve.
262283
* @return the {@link StandardCredentials} or {@code null}
284+
* @deprecated use {@link #lookupScanCredentials(Item, String, String, String)}
263285
*/
286+
@Deprecated
264287
@CheckForNull
265288
public static StandardCredentials lookupScanCredentials(
266289
@CheckForNull Item context,
267290
@CheckForNull String apiUri,
268291
@CheckForNull String scanCredentialsId) {
292+
return lookupScanCredentials(context, apiUri, scanCredentialsId, null);
293+
}
294+
295+
/**
296+
* Resolves the specified scan credentials in the specified context for use against the specified
297+
* API endpoint.
298+
*
299+
* @param context the context.
300+
* @param apiUri the API endpoint.
301+
* @param scanCredentialsId the credentials to resolve.
302+
* @param repoOwner the org/user
303+
* @return the {@link StandardCredentials} or {@code null}
304+
*/
305+
@CheckForNull
306+
public static StandardCredentials lookupScanCredentials(
307+
@CheckForNull Item context,
308+
@CheckForNull String apiUri,
309+
@CheckForNull String scanCredentialsId,
310+
@CheckForNull String repoOwner) {
269311
if (Util.fixEmpty(scanCredentialsId) == null) {
270312
return null;
271313
} else {
272-
return CredentialsMatchers.firstOrNull(
273-
CredentialsProvider.lookupCredentials(
274-
StandardUsernameCredentials.class,
275-
context,
276-
context instanceof Queue.Task
277-
? ((Queue.Task) context).getDefaultAuthentication()
278-
: ACL.SYSTEM,
279-
githubDomainRequirements(apiUri)),
280-
CredentialsMatchers.allOf(
281-
CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()));
314+
StandardCredentials c =
315+
CredentialsMatchers.firstOrNull(
316+
CredentialsProvider.lookupCredentials(
317+
StandardUsernameCredentials.class,
318+
context,
319+
context instanceof Queue.Task
320+
? ((Queue.Task) context).getDefaultAuthentication()
321+
: ACL.SYSTEM,
322+
githubDomainRequirements(apiUri)),
323+
CredentialsMatchers.allOf(
324+
CredentialsMatchers.withId(scanCredentialsId), githubScanCredentialsMatcher()));
325+
if (c instanceof GitHubAppCredentials && repoOwner != null) {
326+
return ((GitHubAppCredentials) c).withOwner(repoOwner);
327+
} else {
328+
return c;
329+
}
282330
}
283331
}
284332

src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

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

5+
import com.cloudbees.jenkins.GitHubRepositoryName;
6+
import com.cloudbees.plugins.credentials.Credentials;
57
import com.cloudbees.plugins.credentials.CredentialsScope;
68
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
79
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
10+
import com.coravy.hudson.plugins.github.GithubProjectProperty;
811
import edu.umd.cs.findbugs.annotations.CheckForNull;
912
import edu.umd.cs.findbugs.annotations.NonNull;
1013
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1114
import hudson.Extension;
1215
import hudson.Functions;
1316
import hudson.Util;
17+
import hudson.model.Job;
18+
import hudson.model.Run;
1419
import hudson.remoting.Channel;
1520
import hudson.util.FormValidation;
1621
import hudson.util.ListBoxModel;
@@ -20,10 +25,13 @@
2025
import java.security.GeneralSecurityException;
2126
import java.time.Duration;
2227
import java.time.Instant;
28+
import java.util.HashMap;
2329
import java.util.List;
30+
import java.util.Map;
2431
import java.util.concurrent.TimeUnit;
2532
import java.util.logging.Level;
2633
import java.util.logging.Logger;
34+
import jenkins.scm.api.SCMSource;
2735
import jenkins.security.SlaveToMasterCallable;
2836
import jenkins.util.JenkinsJVM;
2937
import net.sf.json.JSONObject;
@@ -73,10 +81,18 @@ public class GitHubAppCredentials extends BaseStandardCredentials
7381

7482
private String apiUri;
7583

84+
@SuppressFBWarnings(
85+
value = "IS2_INCONSISTENT_SYNC",
86+
justification = "#withOwner locking only for #byOwner")
7687
private String owner;
7788

7889
private transient AppInstallationToken cachedToken;
7990

91+
/**
92+
* Cache of credentials specialized by {@link #owner}, so that {@link #cachedToken} is preserved.
93+
*/
94+
private transient Map<String, GitHubAppCredentials> byOwner;
95+
8096
@DataBoundConstructor
8197
@SuppressWarnings("unused") // by stapler
8298
public GitHubAppCredentials(
@@ -310,6 +326,47 @@ public String getUsername() {
310326
return appID;
311327
}
312328

329+
@NonNull
330+
public synchronized GitHubAppCredentials withOwner(@NonNull String owner) {
331+
if (this.owner != null) {
332+
if (!owner.equals(this.owner)) {
333+
throw new IllegalArgumentException("Owner mismatch: " + this.owner + " vs. " + owner);
334+
}
335+
return this;
336+
}
337+
if (byOwner == null) {
338+
byOwner = new HashMap<>();
339+
}
340+
return byOwner.computeIfAbsent(
341+
owner,
342+
k -> {
343+
GitHubAppCredentials clone =
344+
new GitHubAppCredentials(getScope(), getId(), getDescription(), appID, privateKey);
345+
clone.apiUri = apiUri;
346+
clone.owner = owner;
347+
return clone;
348+
});
349+
}
350+
351+
@NonNull
352+
@Override
353+
public Credentials forRun(Run<?, ?> context) {
354+
if (owner != null) {
355+
return this;
356+
}
357+
Job<?, ?> job = context.getParent();
358+
SCMSource src = SCMSource.SourceByItem.findSource(job);
359+
if (src instanceof GitHubSCMSource) {
360+
return withOwner(((GitHubSCMSource) src).getRepoOwner());
361+
}
362+
GitHubRepositoryName ghrn =
363+
GitHubRepositoryName.create(job.getProperty(GithubProjectProperty.class));
364+
if (ghrn != null) {
365+
return withOwner(ghrn.userName);
366+
}
367+
return this;
368+
}
369+
313370
private AppInstallationToken getCachedToken() {
314371
synchronized (this) {
315372
return cachedToken;

src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubBuildStatusNotification.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private static GitHub lookUpGitHub(@NonNull Job<?, ?> job) throws IOException {
191191
return Connector.connect(
192192
source.getApiUri(),
193193
Connector.lookupScanCredentials(
194-
job, source.getApiUri(), source.getScanCredentialsId()));
194+
job, source.getApiUri(), source.getScanCredentialsId(), source.getRepoOwner()));
195195
}
196196
}
197197
return null;

src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMFileSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public SCMFileSystem build(
241241
String apiUri = src.getApiUri();
242242
StandardCredentials credentials =
243243
Connector.lookupScanCredentials(
244-
(Item) src.getOwner(), apiUri, src.getScanCredentialsId());
244+
(Item) src.getOwner(), apiUri, src.getScanCredentialsId(), src.getRepoOwner());
245245

246246
// Github client and validation
247247
GitHub github = Connector.connect(apiUri, credentials);

src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubSCMNavigator.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,8 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
935935
}
936936

937937
StandardCredentials credentials =
938-
Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId);
938+
Connector.lookupScanCredentials(
939+
(Item) observer.getContext(), apiUri, credentialsId, repoOwner);
939940

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

12641265
StandardCredentials credentials =
1265-
Connector.lookupScanCredentials((Item) observer.getContext(), apiUri, credentialsId);
1266+
Connector.lookupScanCredentials(
1267+
(Item) observer.getContext(), apiUri, credentialsId, repoOwner);
12661268

12671269
// Github client and validation
12681270
GitHub github;
@@ -1577,8 +1579,8 @@ public List<Action> retrieveActions(
15771579
List<Action> result = new ArrayList<>();
15781580
String apiUri = Util.fixEmptyAndTrim(getApiUri());
15791581
StandardCredentials credentials =
1580-
Connector.lookupScanCredentials((Item) owner, apiUri, credentialsId);
1581-
GitHub hub = Connector.connect(apiUri, credentials);
1582+
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner);
1583+
GitHub hub = Connector.connect(getApiUri(), credentials);
15821584
boolean privateMode = determinePrivateMode(apiUri);
15831585
try {
15841586
Connector.configureLocalRateLimitChecker(listener, hub);
@@ -1631,7 +1633,7 @@ public void afterSave(@NonNull SCMNavigatorOwner owner) {
16311633
try {
16321634
// FIXME MINOR HACK ALERT
16331635
StandardCredentials credentials =
1634-
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId);
1636+
Connector.lookupScanCredentials((Item) owner, getApiUri(), credentialsId, repoOwner);
16351637
GitHub hub = Connector.connect(getApiUri(), credentials);
16361638
try {
16371639
GitHubOrgWebHook.register(hub, repoOwner);
@@ -1757,8 +1759,9 @@ protected SCMSourceCategory[] createCategories() {
17571759
public FormValidation doCheckCredentialsId(
17581760
@CheckForNull @AncestorInPath Item context,
17591761
@QueryParameter String apiUri,
1760-
@QueryParameter String credentialsId) {
1761-
return Connector.checkScanCredentials(context, apiUri, credentialsId);
1762+
@QueryParameter String credentialsId,
1763+
@QueryParameter String repoOwner) {
1764+
return Connector.checkScanCredentials(context, apiUri, credentialsId, repoOwner);
17621765
}
17631766

17641767
/**

0 commit comments

Comments
 (0)