From 4237b24a1c077e75622d6ad6e8a9c3b0f5449899 Mon Sep 17 00:00:00 2001 From: Philipp Dolif Date: Tue, 9 Aug 2022 13:45:25 +0200 Subject: [PATCH 1/5] Add GitHubAppHelper #123 --- social-bot-manager/build.gradle | 5 + .../chat/github/GitHubAppHelper.java | 106 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java diff --git a/social-bot-manager/build.gradle b/social-bot-manager/build.gradle index 67187a53..206736b4 100644 --- a/social-bot-manager/build.gradle +++ b/social-bot-manager/build.gradle @@ -49,6 +49,11 @@ dependencies { implementation "commons-codec:commons-codec:1.13" implementation "com.github.pengrad:java-telegram-bot-api:4.9.0" + // GitHub API + implementation "org.kohsuke:github-api:1.306" + implementation "io.jsonwebtoken:jjwt-impl:0.11.5" + implementation "io.jsonwebtoken:jjwt-jackson:0.11.5" + // javax.websocket-api;version="1.1", jslack;version="1.8.1", rocketchat-common;version="0.7.1, rocketchat-core;version="0.7.1, rocketchat-livechat;version="0.7.1" } diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java new file mode 100644 index 00000000..d2c70c2c --- /dev/null +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java @@ -0,0 +1,106 @@ +package i5.las2peer.services.socialBotManagerService.chat.github; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.kohsuke.github.GHAppInstallation; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; + +import javax.xml.bind.DatatypeConverter; +import java.io.IOException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Date; + +/** + * A GitHub app can be installed multiple times (e.g., within different organizations or repositories). + * To use the GitHub API for an app installation, we need an access token for this app installation. + * For requesting this access token, a JWT is needed. This JWT allows to authenticate as a GitHub app. + * The JWT needs to be signed using the app's private key (from general app settings). + * + * See https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps + */ +public class GitHubAppHelper { + + /** + * Id of the GitHub app. + */ + private int gitHubAppId; + + /** + * Private key used to sign JWTs. + */ + private Key privateKey; + + /** + * + * @param gitHubAppId Id of the GitHub app + * @param pkcs8PrivateKey Private key of GitHub app (already needs to be converted to pkcs8) + * @throws GitHubAppHelperException + */ + public GitHubAppHelper(int gitHubAppId, String pkcs8PrivateKey) throws GitHubAppHelperException { + this.gitHubAppId = gitHubAppId; + + byte[] pkcs8PrivateKeyBytes = DatatypeConverter.parseBase64Binary(pkcs8PrivateKey); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8PrivateKeyBytes); + try { + this.privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new GitHubAppHelperException(e.getMessage()); + } + } + + /** + * Returns a GitHub object that has access to the given repository. + * @param repositoryFullName Full name of the repository, containing both owner and repository name. + * @return GitHub object that has access to the given repository. + */ + public GitHub getGitHubInstance(String repositoryFullName) { + String ownerName = repositoryFullName.split("/")[0]; + String repoName = repositoryFullName.split("/")[1]; + + try { + // first create GitHub object using a JWT (this is needed to request an access token for an app installation) + GitHub gitHub = new GitHubBuilder().withJwtToken(generateJWT(gitHubAppId)).build(); + + // get app installation for given repository (getInstallationByRepository requires a JWT) + GHAppInstallation installation = gitHub.getApp().getInstallationByRepository(ownerName, repoName); + + // create a GitHub object with app installation token + return new GitHubBuilder().withAppInstallationToken(installation.createToken().create().getToken()).build(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Generates a JWT and signs it with the app's private key. + * @param gitHubAppId Id of the GitHub app + * @return JWT + */ + private String generateJWT(int gitHubAppId) { + long nowMillis = System.currentTimeMillis(); + Date now = new Date(nowMillis); + Date expiration = new Date(nowMillis + 60000); + return Jwts.builder() + .setIssuedAt(now) // issue now + .setExpiration(expiration) // expiration time of JWT + .setIssuer(String.valueOf(gitHubAppId)) // app id needs to be used as issuer + .signWith(this.privateKey, SignatureAlgorithm.RS256) // sign with app's private key + .compact(); + } + + /** + * General exception that is thrown if something related to the GitHubAppHelper is not working. + */ + public class GitHubAppHelperException extends Exception { + public GitHubAppHelperException(String message) { + super(message); + } + } + +} From 7fcbd51026cf73ea5fc5ddbb5d481d34e0e4aa71 Mon Sep 17 00:00:00 2001 From: Philipp Dolif Date: Tue, 9 Aug 2022 15:24:45 +0200 Subject: [PATCH 2/5] Add chat mediators for issues and pull requests #123 --- .../chat/github/GitHubChatMediator.java | 234 ++++++++++++++++++ .../chat/github/GitHubIssueMediator.java | 40 +++ .../chat/github/GitHubPRMediator.java | 50 ++++ 3 files changed, 324 insertions(+) create mode 100644 social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubChatMediator.java create mode 100644 social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubIssueMediator.java create mode 100644 social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubPRMediator.java diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubChatMediator.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubChatMediator.java new file mode 100644 index 00000000..fdc043f0 --- /dev/null +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubChatMediator.java @@ -0,0 +1,234 @@ +package i5.las2peer.services.socialBotManagerService.chat.github; + +import i5.las2peer.services.socialBotManagerService.chat.AuthTokenException; +import i5.las2peer.services.socialBotManagerService.chat.ChatMessage; +import i5.las2peer.services.socialBotManagerService.chat.ChatMessageCollector; +import i5.las2peer.services.socialBotManagerService.chat.EventChatMediator; +import net.minidev.json.JSONObject; +import org.kohsuke.github.GitHub; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Vector; + +/** + * Parent class for GitHub issue and pull request chat mediators. + *

+ * In the GitHubChatMediator, a channel is a comment section of an issue or a pull request. + * Therefore, the "messenger channel" is defined as follows: + * [Owner name]/[Repo name]#[Issue or PR number] + */ +public abstract class GitHubChatMediator extends EventChatMediator { + + /** + * Message collector: New comments are added to the collector in {@link #handleEvent(JSONObject) handleEvent}. + */ + private ChatMessageCollector messageCollector; + + /** + * Helper for the GitHub app that is used by the chat mediator. + */ + private GitHubAppHelper gitHubAppHelper; + + /** + * Id of the GitHub app that is used by the chat mediator. + */ + private int gitHubAppId; + + /** + * Event name for new comments is the same for both issues and pull requests. + */ + private final String eventNameItemComment = "issue_comment"; + + /** + * The action name for new comments is the same for both issues and pull requests. + */ + private final String actionNameItemComment = "created"; + + /** + * Event name for a newly opened issue or pull request. + */ + protected String eventNameItemOpened; + + /** + * Action name for a newly opened issue or pull request. + */ + private final String actionNameItemOpened = "opened"; + + /** + * Name of the field that contains the comment information (same for issues and pull requests). + */ + private final String itemNameComment = "issue"; + + /** + * Name of the field that contains the text of a newly opened issue or pull request. + */ + protected String itemNameOpened; + + /** + * Constructor for GitHub chat mediators. + * + * @param authToken Format: [GitHub app id]:[GitHub app private key in pkcs8] + * @throws GitHubAppHelper.GitHubAppHelperException If something related to the GitHubAppHelper is not working. + * @throws AuthTokenException If format of {@code authToken} is incorrect. + */ + public GitHubChatMediator(String authToken) throws GitHubAppHelper.GitHubAppHelperException, AuthTokenException { + super(authToken); + + // use default message collector + this.messageCollector = new ChatMessageCollector(); + + // check that authToken contains app id and private key + String[] parts = authToken.split(":"); + if (parts.length != 2) { + throw new AuthTokenException("Incorrect auth information, format should be: " + + "[GitHub app id]:[GitHub app private key in pkcs8]"); + } + + // get app id and private key + this.gitHubAppId = Integer.parseInt(parts[0]); + String pkcs8PrivateKey = parts[1]; + + // init GitHub app helper + this.gitHubAppHelper = new GitHubAppHelper(this.gitHubAppId, pkcs8PrivateKey); + } + + /** + * Used to filter out events that are not relevant for the chat mediators. + * + * @param parsedEvent Event + * @return Whether the given event is relevant for the chat mediators. + */ + protected boolean isRelevantEvent(JSONObject parsedEvent) { + String event = parsedEvent.getAsString("event"); + return List.of(eventNameItemComment, eventNameItemOpened).contains(event); + } + + /** + * Adds new comment to {@link GitHubChatMediator#messageCollector messageCollector} (if given event contains one). + * + * @param parsedEvent JSON representation of incoming GitHub event + */ + @Override + public void handleEvent(JSONObject parsedEvent) { + // extract name and payload of given event + String eventName = parsedEvent.getAsString("event"); + JSONObject payload = (JSONObject) parsedEvent.get("payload"); + + String repositoryFullName = this.getRepositoryFullNameOfEvent(parsedEvent); + String action = payload.getAsString("action"); + + boolean itemComment = eventName.equals(eventNameItemComment) && action.equals(actionNameItemComment); + boolean itemOpened = eventName.equals(eventNameItemOpened) && action.equals(actionNameItemOpened); + + if (itemComment || itemOpened) { + String itemName = itemComment ? itemNameComment : itemNameOpened; + JSONObject item = (JSONObject) payload.get(itemName); + String channelName = repositoryFullName + "#" + item.getAsNumber("number"); + + JSONObject comment; + if (itemComment) comment = (JSONObject) payload.get("comment"); + else if (itemOpened) comment = (JSONObject) payload.get(itemName); + else return; + + // extract user info from comment + JSONObject user = (JSONObject) comment.get("user"); + String username = user.getAsString("login"); + String message = comment.getAsString("body"); + + // dont handle bot messages + if (this.isBotAccount(user)) return; + + // add comment to message collector + ChatMessage chatMessage = new ChatMessage(channelName, username, message); + this.messageCollector.addMessage(chatMessage); + } + } + + /** + * Comments on an issue or pull request. As in GitHub a pull request also seems to be an issue, this method can + * be shared for both chat mediators. + * + * @param channel Format: [Owner name]/[Repo name]#[Issue or PR number] + * @param text The content of the comment + * @param id + */ + @Override + public void sendMessageToChannel(String channel, String text, Optional id) { + String repositoryFullName = channel.split("#")[0]; + int number = Integer.parseInt(channel.split("#")[1]); + + try { + GitHub instance = this.gitHubAppHelper.getGitHubInstance(repositoryFullName); + if (instance != null) { + // post comment (in GitHub a pull request also seems to be an issue) + instance.getRepository(repositoryFullName).getIssue(number).comment(text); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public Vector getMessages() { + return this.messageCollector.getMessages(); + } + + /** + * Returns the id of the GitHub app that the chat mediator is using. + * + * @return Id of the GitHub app that the chat mediator is using. + */ + public int getGitHubAppId() { + return this.gitHubAppId; + } + + /** + * Extracts the full repository name from an event JSONObject. + * + * @param parsedEvent Event + * @return Full name of the repository, containing both owner and repository name. + */ + private String getRepositoryFullNameOfEvent(JSONObject parsedEvent) { + JSONObject payload = (JSONObject) parsedEvent.get("payload"); + JSONObject repository = (JSONObject) payload.get("repository"); + return repository.getAsString("full_name"); + } + + /** + * Checks if the given user (from GitHub) is a bot. + * + * @param user User JSONObject from GitHub + * @return Whether the given user is a bot. + */ + private boolean isBotAccount(JSONObject user) { + return user.getAsString("type").equals("Bot"); + } + + @Override + public void editMessage(String channel, String messageId, String message, Optional id) { + } + + @Override + public void sendBlocksMessageToChannel(String channel, String blocks, String authToken, Optional id) { + } + + @Override + public void updateBlocksMessageToChannel(String channel, String blocks, String authToken, String ts, Optional id) { + } + + @Override + public void sendFileMessageToChannel(String channel, File f, String text, Optional id) { + } + + @Override + public String getChannelByEmail(String email) { + return null; + } + + @Override + public void close() { + } +} diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubIssueMediator.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubIssueMediator.java new file mode 100644 index 00000000..f40a482f --- /dev/null +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubIssueMediator.java @@ -0,0 +1,40 @@ +package i5.las2peer.services.socialBotManagerService.chat.github; + +import i5.las2peer.services.socialBotManagerService.chat.AuthTokenException; +import net.minidev.json.JSONObject; + +/** + * Chat mediator for comments on GitHub issues. + */ +public class GitHubIssueMediator extends GitHubChatMediator { + + /** + * Constructor for GitHub issue chat mediators. + * + * @param authToken Format: [GitHub app id]:[GitHub app private key in pkcs8] + * @throws GitHubAppHelper.GitHubAppHelperException If something related to the GitHubAppHelper is not working. + * @throws AuthTokenException If format of {@code authToken} is incorrect. + */ + public GitHubIssueMediator(String authToken) throws GitHubAppHelper.GitHubAppHelperException, AuthTokenException { + super(authToken); + this.eventNameItemOpened = "issues"; + this.itemNameOpened = "issue"; + } + + /** + * Adds new issue comment to the message collector (if given event contains one). + * + * @param parsedEvent JSON representation of incoming GitHub event + */ + @Override + public void handleEvent(JSONObject parsedEvent) { + if (!this.isRelevantEvent(parsedEvent)) return; + + // check if event belongs to an issue + JSONObject payload = (JSONObject) parsedEvent.get("payload"); + JSONObject issue = (JSONObject) payload.get("issue"); + if (!issue.containsKey("pull_request")) { + super.handleEvent(parsedEvent); + } + } +} diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubPRMediator.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubPRMediator.java new file mode 100644 index 00000000..ae8d7623 --- /dev/null +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubPRMediator.java @@ -0,0 +1,50 @@ +package i5.las2peer.services.socialBotManagerService.chat.github; + +import i5.las2peer.services.socialBotManagerService.chat.AuthTokenException; +import net.minidev.json.JSONObject; + +/** + * Chat mediator for comments on GitHub pull requests. + */ +public class GitHubPRMediator extends GitHubChatMediator { + + /** + * Constructor for GitHub pull request chat mediators. + * + * @param authToken Format: [GitHub app id]:[GitHub app private key in pkcs8] + * @throws GitHubAppHelper.GitHubAppHelperException If something related to the GitHubAppHelper is not working. + * @throws AuthTokenException If format of {@code authToken} is incorrect. + */ + public GitHubPRMediator(String authToken) throws GitHubAppHelper.GitHubAppHelperException, AuthTokenException { + super(authToken); + this.eventNameItemOpened = "pull_request"; + this.itemNameOpened = "pull_request"; + } + + /** + * Adds new pull request comment to the message collector (if given event contains one). + * + * @param parsedEvent JSON representation of incoming GitHub event + */ + @Override + public void handleEvent(JSONObject parsedEvent) { + if (!this.isRelevantEvent(parsedEvent)) return; + + // check if event belongs to a pull request + JSONObject payload = (JSONObject) parsedEvent.get("payload"); + // note: in GitHub a pull request also seems to be an issue + if (payload.containsKey("issue")) { + // could be a pull request comment + JSONObject issue = (JSONObject) payload.get("issue"); + if (issue.containsKey("pull_request")) { + // event belongs to a pull request (and not to an issue) + super.handleEvent(parsedEvent); + } + } else { + // could be a newly opened pull request + if (payload.containsKey("pull_request")) { + super.handleEvent(parsedEvent); + } + } + } +} From 5182bd21167fb99ca1bb336d4cffa0a4451d2a9d Mon Sep 17 00:00:00 2001 From: Philipp Dolif Date: Tue, 9 Aug 2022 16:37:14 +0200 Subject: [PATCH 3/5] Add mediators to ChatService.java and Messenger.java #123 --- .../chat/ChatService.java | 8 ++++++++ .../model/Messenger.java | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/ChatService.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/ChatService.java index 9206f382..6e0f43c3 100644 --- a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/ChatService.java +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/ChatService.java @@ -1,6 +1,8 @@ package i5.las2peer.services.socialBotManagerService.chat; import com.fasterxml.jackson.annotation.JsonProperty; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubIssueMediator; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubPRMediator; /** * This enum lists all available messenger services. The string value has to @@ -23,6 +25,12 @@ public enum ChatService { @JsonProperty("Moodle Forum") MOODLE_FORUM("Moodle Forum", MoodleForumMediator.class), + @JsonProperty("GitHub Issues") + GITHUB_ISSUES("GitHub Issues", GitHubIssueMediator.class), + + @JsonProperty("GitHub Pull Requests") + GITHUB_PR("GitHub Pull Requests", GitHubPRMediator.class), + UNKNOWN("", null); /** diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/model/Messenger.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/model/Messenger.java index e36163a2..4d904fdf 100644 --- a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/model/Messenger.java +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/model/Messenger.java @@ -18,7 +18,9 @@ import javax.websocket.DeploymentException; import i5.las2peer.services.socialBotManagerService.chat.*; -import i5.las2peer.services.socialBotManagerService.chat.xAPI.ChatStatement; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubAppHelper; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubIssueMediator; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubPRMediator; import i5.las2peer.services.socialBotManagerService.database.SQLDatabase; import i5.las2peer.services.socialBotManagerService.nlu.Entity; import i5.las2peer.services.socialBotManagerService.nlu.Intent; @@ -86,6 +88,20 @@ public Messenger(String id, String chatService, String token, SQLDatabase databa case MOODLE_FORUM: this.chatMediator = new MoodleForumMediator(token); break; + case GITHUB_ISSUES: + try { + this.chatMediator = new GitHubIssueMediator(token); + } catch (GitHubAppHelper.GitHubAppHelperException e) { + throw new AuthTokenException(e.getMessage()); + } + break; + case GITHUB_PR: + try { + this.chatMediator = new GitHubPRMediator(token); + } catch (GitHubAppHelper.GitHubAppHelperException e) { + throw new AuthTokenException(e.getMessage()); + } + break; default: throw new ParseBotException("Unimplemented chat service: " + chatService); } From 97a5a6180d8bf8565519de941daccd6ec5949b44 Mon Sep 17 00:00:00 2001 From: Philipp Dolif Date: Tue, 9 Aug 2022 16:56:57 +0200 Subject: [PATCH 4/5] Add GitHub webhook receiver #123 --- .../SocialBotManagerService.java | 2 + .../chat/github/GitHubWebhookReceiver.java | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubWebhookReceiver.java diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/SocialBotManagerService.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/SocialBotManagerService.java index dee1b68c..885bec4d 100644 --- a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/SocialBotManagerService.java +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/SocialBotManagerService.java @@ -86,6 +86,7 @@ import i5.las2peer.restMapper.annotations.ServicePath; import i5.las2peer.security.BotAgent; import i5.las2peer.services.socialBotManagerService.chat.*; +import i5.las2peer.services.socialBotManagerService.chat.github.GitHubWebhookReceiver; import i5.las2peer.services.socialBotManagerService.chat.xAPI.ChatStatement; import i5.las2peer.services.socialBotManagerService.database.SQLDatabase; import i5.las2peer.services.socialBotManagerService.database.SQLDatabaseType; @@ -240,6 +241,7 @@ protected void initResources() { getResourceConfig().register(BotModelResource.class); getResourceConfig().register(TrainingResource.class); getResourceConfig().register(this); + getResourceConfig().register(GitHubWebhookReceiver.class); } @POST diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubWebhookReceiver.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubWebhookReceiver.java new file mode 100644 index 00000000..c98571bf --- /dev/null +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubWebhookReceiver.java @@ -0,0 +1,69 @@ +package i5.las2peer.services.socialBotManagerService.chat.github; + +import i5.las2peer.api.Context; +import i5.las2peer.services.socialBotManagerService.SocialBotManagerService; +import i5.las2peer.services.socialBotManagerService.chat.ChatService; +import i5.las2peer.services.socialBotManagerService.model.Bot; +import i5.las2peer.services.socialBotManagerService.model.Messenger; +import i5.las2peer.services.socialBotManagerService.model.VLE; +import io.swagger.annotations.Api; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; + +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; +import java.util.Collection; + +@Api(value = "GitHub Webhook Receiver Resource") +@Path("/github") +public class GitHubWebhookReceiver { + + /** + * Receives incoming webhook events from a GitHub app and sends them to related GitHub chat mediators. + * + * @param body Event + * @param eventName Name of event + * @param gitHubAppId Id of GitHub app + * @return 200 + */ + @POST + @Path("/webhook/{gitHubAppId}") + public Response receiveWebhookEvent(String body, @HeaderParam("X-GitHub-Event") String eventName, + @PathParam("gitHubAppId") int gitHubAppId) { + JSONObject payload = (JSONObject) JSONValue.parse(body); + + // put name of event and payload into one JSONObject + JSONObject eventObj = new JSONObject(); + eventObj.put("event", eventName); + eventObj.put("payload", payload); + + SocialBotManagerService service = (SocialBotManagerService) Context.get().getService(); + + // need to find bot(s) that use this GitHub app id + Collection vles = service.getConfig().getVLEs().values(); + for (VLE vle : vles) { + for (Bot bot : vle.getBots().values()) { + Messenger messenger = bot.getMessenger(ChatService.GITHUB_ISSUES); + if (messenger != null) { + GitHubIssueMediator mediator = (GitHubIssueMediator) messenger.getChatMediator(); + if (mediator.getGitHubAppId() == gitHubAppId) { + mediator.handleEvent(eventObj); + } + } + + messenger = bot.getMessenger(ChatService.GITHUB_PR); + if (messenger != null) { + GitHubPRMediator mediator = (GitHubPRMediator) messenger.getChatMediator(); + if (mediator.getGitHubAppId() == gitHubAppId) { + mediator.handleEvent(eventObj); + } + } + } + } + + return Response.status(200).build(); + } +} From 87677647ed9ba75abccb3fb9011febf385a37c28 Mon Sep 17 00:00:00 2001 From: Philipp Dolif Date: Tue, 9 Aug 2022 18:51:17 +0200 Subject: [PATCH 5/5] Refactor GitHubAppHelper #123 --- .../socialBotManagerService/chat/github/GitHubAppHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java index d2c70c2c..9838b2b7 100644 --- a/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java +++ b/social-bot-manager/src/main/java/i5/las2peer/services/socialBotManagerService/chat/github/GitHubAppHelper.java @@ -64,7 +64,7 @@ public GitHub getGitHubInstance(String repositoryFullName) { try { // first create GitHub object using a JWT (this is needed to request an access token for an app installation) - GitHub gitHub = new GitHubBuilder().withJwtToken(generateJWT(gitHubAppId)).build(); + GitHub gitHub = new GitHubBuilder().withJwtToken(generateJWT()).build(); // get app installation for given repository (getInstallationByRepository requires a JWT) GHAppInstallation installation = gitHub.getApp().getInstallationByRepository(ownerName, repoName); @@ -79,10 +79,9 @@ public GitHub getGitHubInstance(String repositoryFullName) { /** * Generates a JWT and signs it with the app's private key. - * @param gitHubAppId Id of the GitHub app * @return JWT */ - private String generateJWT(int gitHubAppId) { + private String generateJWT() { long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); Date expiration = new Date(nowMillis + 60000);