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

Skip GitHubOrgWebHook.register when using a GH app receiving all relevant events #289

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
7 changes: 7 additions & 0 deletions docs/github-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ Permissions this plugin uses:
- Pull requests: Read-only
- Webhooks (optional) - If you want the plugin to manage webhooks for you, Read and Write

You are not required to use webhooks but it strongly recommeded over alternatives such as polling. To use webhooks, you can give the your GitHub App "Read and Write" permission to Webhooks (noted above) and the plugin will configure your repositories to send the appropriate events. Otherwise you can manually configure the app handle all events needed by this plugin, in which case the app will not need webhook permissions.
jglick marked this conversation as resolved.
Show resolved Hide resolved

You should configure the app to receive at least the following events:

- Pull request
- Push
- Repository (automatically add required read-only "Administration" permission)

Click 'Create GitHub app'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,41 @@ public Secret getPrivateKey() {
}

@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) {
try {
String jwtToken = createJWT(appId, appPrivateKey);
GitHub gitHubApp = new GitHubBuilder().withEndpoint(apiUrl).withJwtToken(jwtToken).build();
@CheckForNull
GHAppInstallation getAppInstallation() {
String jwtToken = createJWT(appID, privateKey.getPlainText());
GitHubBuilder ghb = new GitHubBuilder();
if (Util.fixEmpty(apiUri) != null) {
ghb = ghb.withEndpoint(apiUri);
}
GitHub gitHubApp = ghb.withJwtToken(jwtToken).build();

GHApp app = gitHubApp.getApp();

GHApp app = gitHubApp.getApp();
List<GHAppInstallation> appInstallations = app.listInstallations().asList();
if (appInstallations.isEmpty()) {
return null;
} else {
// TODO make sensitive to contextual organization
jglick marked this conversation as resolved.
Show resolved Hide resolved
return appInstallations.get(0);
}
}

List<GHAppInstallation> appInstallations = app.listInstallations().asList();
if (!appInstallations.isEmpty()) {
GHAppInstallation appInstallation = appInstallations.get(0);
@SuppressWarnings("deprecation") // preview features are required for GitHub app integration, GitHub api adds deprecated to all preview methods
private String generateAppInstallationToken() {
try {
GHAppInstallation appInstallation = getAppInstallation();
if (appInstallation != null) {
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), e);
}
throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appId));
throw new IllegalArgumentException(String.format(ERROR_AUTHENTICATING_GITHUB_APP, appID));
}

/**
Expand All @@ -101,11 +116,7 @@ static String generateAppInstallationToken(String appId, String appPrivateKey, S
@NonNull
@Override
public Secret getPassword() {
if (Util.fixEmpty(apiUri) == null) {
apiUri = "https://api.github.com";
}

String appInstallationToken = generateAppInstallationToken(appID, privateKey.getPlainText(), apiUri);
String appInstallationToken = generateAppInstallationToken();

return Secret.fromString(appInstallationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ private static File getTrackingFile(String orgName) {
return new File(Jenkins.get().getRootDir(), "github-webhooks/GitHubOrgHook." + orgName);
}

// TODO never called?

Choose a reason for hiding this comment

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

The uninstallation of an app sends out an "action":"deleted" installation event, similar to installation of an app which also sends out an installation event but with "action":"created"

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes but AFAICT this Java method is not called.

Choose a reason for hiding this comment

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

Yeah looking at it, it might be designed to act on a deregister for a webhook, but I cannot find any such event for it on the Github API document :/

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the intention was for this to be called when the OrganizationFolder was deleted, but from e424aa2 I am guessing this was never implemented.

public static void deregister(GitHub hub, String orgName) throws IOException {
String rootUrl = Jenkins.get().getRootUrl();
if (rootUrl == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.RestrictedSince;
import hudson.Util;
import hudson.console.HyperlinkNote;
Expand All @@ -43,6 +44,7 @@
import hudson.util.ListBoxModel;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
Expand Down Expand Up @@ -84,6 +86,7 @@
import org.jenkins.ui.icon.IconSpec;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.github.config.GitHubServerConfig;
import org.jenkinsci.plugins.github.extension.GHEventsSubscriber;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -100,6 +103,7 @@
import org.kohsuke.stapler.interceptor.RequirePOST;

import static org.jenkinsci.plugins.github_branch_source.Connector.isCredentialValid;
import org.kohsuke.github.GHEvent;

public class GitHubSCMNavigator extends SCMNavigator {

Expand Down Expand Up @@ -1247,6 +1251,28 @@ public void afterSave(@NonNull SCMNavigatorOwner owner) {
try {
// FIXME MINOR HACK ALERT
StandardCredentials credentials = Connector.lookupScanCredentials((Item)owner, getApiUri(), credentialsId);
if (credentials instanceof GitHubAppCredentials) {
try {
List<GHEvent> handledEvents = ((GitHubAppCredentials) credentials).getAppInstallation().getEvents();
Method eventsM = GHEventsSubscriber.class.getMethod("events"); // TODO protected
eventsM.setAccessible(true);
boolean good = true;
for (GHEventsSubscriber subscriber : GHEventsSubscriber.all()) {
@SuppressWarnings("unchecked")
Set<GHEvent> subscribedEvents = (Set<GHEvent>) eventsM.invoke(subscriber);
if (!handledEvents.containsAll(subscribedEvents)) {

Choose a reason for hiding this comment

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

This would make sure no events go unhandled :+1

Copy link
Member Author

Choose a reason for hiding this comment

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

It might be too strict—to activate this patch you need to include the repository events which you might otherwise have skipped if you are only dealing with a small list of repositories—but I erred on the conservative side.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this will break on PingGHEventSubscriber. Not yet tested.

DescriptorImpl.LOGGER.log(Level.WARNING, "The GitHub App is not configured to receive some desired events: {0}", subscribedEvents);
good = false;
}
}
if (good) {
DescriptorImpl.LOGGER.info("The GitHub App is configured to receive some desired events; no additional webhook need be installed on the organization");
return;
}
} catch (Exception x) {
DescriptorImpl.LOGGER.log(Level.WARNING, "Could not check whether the GitHub App receives all desired events", x);
}
}
GitHub hub = Connector.connect(getApiUri(), credentials);
try {
GitHubOrgWebHook.register(hub, repoOwner);
Expand Down