diff --git a/docs/github-app.adoc b/docs/github-app.adoc index 3ac990e9e..9c31da02e 100644 --- a/docs/github-app.adoc +++ b/docs/github-app.adoc @@ -87,7 +87,8 @@ Fill out the form: - App ID: the github app ID, it can be found in the 'About' section of your GitHub app in the general tab. - API endpoint (optional, only required for GitHub enterprise this will only show up if a GitHub enterprise server is configured). - Key: click add, paste the contents of the converted private key -- Passphrase: do not fill this field, it will be ignored +- Advanced: (optional) If you've installed your same GitHub app on multiple organizations you need the next step + * Owner: the name of the organisation or user, i.e. jenkinsci for https://github.com/jenkinsci - Click OK === link:https://github.com/jenkinsci/configuration-as-code-plugin[Configuration as Code Plugin] diff --git a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java index f5d5a176f..b191ad9bd 100644 --- a/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java +++ b/src/main/java/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials.java @@ -30,6 +30,9 @@ public class GitHubAppCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { private static final String ERROR_AUTHENTICATING_GITHUB_APP = "Couldn't authenticate with GitHub app ID %s"; + private static final String NOT_INSTALLED = ", has it been installed to your GitHub organisation / user?"; + + private static final String ERROR_NOT_INSTALLED = ERROR_AUTHENTICATING_GITHUB_APP + NOT_INSTALLED; @NonNull private final String appID; @@ -39,6 +42,8 @@ public class GitHubAppCredentials extends BaseStandardCredentials implements Sta private String apiUri; + private String owner; + @DataBoundConstructor @SuppressWarnings("unused") // by stapler public GitHubAppCredentials( @@ -72,8 +77,26 @@ public Secret getPrivateKey() { return privateKey; } + /** + * Owner of this installation, i.e. a user or organisation, + * used to differeniate app installations when the app is installed to multiple organisations / users. + * + * If this is null then call listInstallations and if there's only one in the list then use that installation. + * + * @return the owner of the organisation or null. + */ + @CheckForNull + public String getOwner() { + return owner; + } + + @DataBoundSetter + public void setOwner(String owner) { + this.owner = Util.fixEmpty(owner); + } + @SuppressWarnings("deprecation") // preview features are required for GitHub app integration, GitHub api adds deprecated to all preview methods - static String generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl) { + static String generateAppInstallationToken(String appId, String appPrivateKey, String apiUrl, String owner) { try { String jwtToken = createJWT(appId, appPrivateKey); GitHub gitHubApp = new GitHubBuilder().withEndpoint(apiUrl).withJwtToken(jwtToken).build(); @@ -81,18 +104,28 @@ static String generateAppInstallationToken(String appId, String appPrivateKey, S GHApp app = gitHubApp.getApp(); List appInstallations = app.listInstallations().asList(); - if (!appInstallations.isEmpty()) { - GHAppInstallation appInstallation = appInstallations.get(0); - GHAppInstallationToken appInstallationToken = appInstallation - .createToken(appInstallation.getPermissions()) - .create(); - - return appInstallationToken.getToken(); + if (appInstallations.isEmpty()) { + throw new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId)); } + GHAppInstallation appInstallation; + if (appInstallations.size() == 1) { + appInstallation = appInstallations.get(0); + } else { + appInstallation = appInstallations.stream() + .filter(installation -> installation.getAccount().getLogin().equals(owner)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(String.format(ERROR_NOT_INSTALLED, appId))); + } + + GHAppInstallationToken appInstallationToken = appInstallation + .createToken(appInstallation.getPermissions()) + .create(); + + return appInstallationToken.getToken(); } catch (IOException e) { throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId), e); } - throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId)); + } /** @@ -105,7 +138,7 @@ public Secret getPassword() { apiUri = "https://api.github.com"; } - String appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri); + String appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri, owner); return Secret.fromString(appInstallationToken); } @@ -174,7 +207,8 @@ public FormValidation doCheckAppID(@QueryParameter String appID) { public FormValidation doTestConnection( @QueryParameter("appID") final String appID, @QueryParameter("privateKey") final String privateKey, - @QueryParameter("apiUri") final String apiUri + @QueryParameter("apiUri") final String apiUri, + @QueryParameter("owner") final String owner ) { GitHubAppCredentials gitHubAppCredential = new GitHubAppCredentials( @@ -182,10 +216,10 @@ public FormValidation doTestConnection( appID, Secret.fromString(privateKey) ); gitHubAppCredential.setApiUri(apiUri); + gitHubAppCredential.setOwner(owner); try { GitHub connect = Connector.connect(apiUri, gitHubAppCredential); - return FormValidation.ok("Success, Remaining rate limit: " + connect.getRateLimit().getRemaining()); } catch (Exception e) { return FormValidation.error(e, String.format(ERROR_AUTHENTICATING_GITHUB_APP, appID)); diff --git a/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/config.jelly b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/config.jelly index 5efc33569..a6fcfeaeb 100644 --- a/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/config.jelly @@ -24,7 +24,13 @@ + + + + + + + method="testConnection" with="appID,apiUri,privateKey,owner" /> diff --git a/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/help-owner.html b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/help-owner.html new file mode 100644 index 000000000..2097e42f0 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/github_branch_source/GitHubAppCredentials/help-owner.html @@ -0,0 +1,3 @@ +

+ The organisation or user that this app is to be used for. Only required if this app is installed to multiple organisations. +