Commit 0ab14a67 authored by Greg Messner's avatar Greg Messner
Browse files

Initial commit in support of System Hooks API (#117).

parent a9fb9b33
package org.gitlab4j.api;
import javax.servlet.http.HttpServletRequest;
/**
* This class provides a base class handler for processing GitLab Web Hook and System Hook callouts.
*/
public abstract class HookManager {
private String secretToken;
/**
* Create a HookManager to handle GitLab hook events.
*/
public HookManager() {
this.secretToken = null;
}
/**
* Create a HookManager to handle GitLab hook events which will be verified
* against the specified secretToken.
*
* @param secretToken the secret token to verify against
*/
public HookManager(String secretToken) {
this.secretToken = secretToken;
}
/**
* Set the secret token that received hook events should be validated against.
*
* @param secretToken the secret token to verify against
*/
public void setSecretToken(String secretToken) {
this.secretToken = secretToken;
}
/**
* Validate the provided secret token against the reference secret token. Returns true if
* the secret token is valid or there is no reference secret token to validate against,
* otherwise returns false.
*
* @param secretToken the token to validate
* @return true if the secret token is valid or there is no reference secret token to validate against
*/
public boolean isValidSecretToken(String secretToken) {
return (this.secretToken == null || this.secretToken.equals(secretToken) ? true : false);
}
/**
* Validate the provided secret token found in the HTTP header against the reference secret token.
* Returns true if the secret token is valid or there is no reference secret token to validate
* against, otherwise returns false.
*
* @param request the HTTP request to verify the secret token
* @return true if the secret token is valid or there is no reference secret token to validate against
*/
public boolean isValidSecretToken(HttpServletRequest request) {
if (this.secretToken != null) {
String secretToken = request.getHeader("X-Gitlab-Token");
return (isValidSecretToken(secretToken));
}
return (true);
}
/**
* Parses and verifies an Event instance from the HTTP request and
* fires it off to the registered listeners.
*
* @param request the HttpServletRequest to read the Event instance from
* @throws GitLabApiException if the parsed event is not supported
*/
public abstract void handleEvent(HttpServletRequest request) throws GitLabApiException;
}
\ No newline at end of file
package org.gitlab4j.api;
import java.util.List;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import org.gitlab4j.api.GitLabApi.ApiVersion;
import org.gitlab4j.api.models.SystemHook;
/**
* This class implements the client side API for the GitLab System Hooks Keys API calls.
*/
public class SystemHooksApi extends AbstractApi {
public SystemHooksApi(GitLabApi gitLabApi) {
super(gitLabApi);
}
/**
* Get a list of all system hooks. This method requires admin access.
* Only returns the first page. This method requires admin access.
*
* <code>GET /hooks</code>
*
* @return a list of SystemHookEvent
* @throws GitLabApiException if any exception occurs
*/
public List<SystemHook> getSystemHooks() throws GitLabApiException {
return (getSystemHooks(1, getDefaultPerPage()));
}
/**
* Get a list of all system hooks using the specified page and per page settings.
* This method requires admin access.
*
* <code>GET /hooks</code>
*
* @param page the page to get
* @param perPage the number of deploy keys per page
* @return the list of SystemHookEvent in the specified range
* @throws GitLabApiException if any exception occurs
*/
public List<SystemHook> getSystemHooks(int page, int perPage) throws GitLabApiException {
Response response = get(Response.Status.OK, getPageQueryParams(page, perPage), "hooks");
return (response.readEntity(new GenericType<List<SystemHook>>() {}));
}
/**
* Get a Pager of all system hooks. This method requires admin access.
*
* <code>GET /hooks</code>
*
* @param itemsPerPage the number of SystemHookEvent instances that will be fetched per page
* @return a Pager of SystemHookEvent
* @throws GitLabApiException if any exception occurs
*/
public Pager<SystemHook> getSystemHooks(int itemsPerPage) throws GitLabApiException {
return (new Pager<SystemHook>(this, SystemHook.class, itemsPerPage, null, "hooks"));
}
/**
* Add a new system hook. This method requires admin access.
*
* <code>POST /hooks</code>
*
* @param url the hook URL, required
* @param token secret token to validate received payloads, optional
* @param pushEvents when true, the hook will fire on push events, optional
* @param tagPushEvents when true, the hook will fire on new tags being pushed, optional
* @param enablSsslVerification do SSL verification when triggering the hook, optional
* @return an SystemHookEvent instance with info on the added system hook
* @throws GitLabApiException if any exception occurs
*/
public SystemHook addSystemHook(String url, String token, Boolean pushEvents,
Boolean tagPushEvents, Boolean enablSsslVerification) throws GitLabApiException {
if (url == null) {
throw new RuntimeException("url cannot be null");
}
GitLabApiForm formData = new GitLabApiForm()
.withParam("url", url, true)
.withParam("token", token)
.withParam("push_events", pushEvents)
.withParam("tag_push_events", tagPushEvents)
.withParam("enable_ssl_verification", enablSsslVerification);
Response response = post(Response.Status.CREATED, formData, "hooks");
return (response.readEntity(SystemHook.class));
}
/**
* Deletes a system hook. This method requires admin access.
*
* <code>DELETE /hooks/:hook_id</code>
*
* @param hook the SystemHook instance to delete
* @throws GitLabApiException if any exception occurs
*/
public void deleteSystemHook(SystemHook hook) throws GitLabApiException {
if (hook == null) {
throw new RuntimeException("hook cannot be null");
}
deleteSystemHook(hook.getId());
}
/**
* Deletes a system hook. This method requires admin access.
*
* <code>DELETE /hooks/:hook_id</code>
*
* @param hookId the ID of the system hook to delete
* @throws GitLabApiException if any exception occurs
*/
public void deleteSystemHook(Integer hookId) throws GitLabApiException {
if (hookId == null) {
throw new RuntimeException("hookId cannot be null");
}
Response.Status expectedStatus = (isApiVersion(ApiVersion.V3) ? Response.Status.OK : Response.Status.NO_CONTENT);
delete(expectedStatus, null, "hooks", hookId);
}
/**
* Test a system hook. This method requires admin access.
*
* <code>GET /hooks/:hook_id</code>
*
* @param hook the SystemHookEvent instance to test
* @throws GitLabApiException if any exception occurs
*/
public void testSystemHook(SystemHook hook) throws GitLabApiException {
if (hook == null) {
throw new RuntimeException("hook cannot be null");
}
testSystemHook(hook.getId());
}
/**
* Test a system hook. This method requires admin access.
*
* <code>GET /hooks/:hook_id</code>
*
* @param hookId the ID of the system hook to test
* @throws GitLabApiException if any exception occurs
*/
public void testSystemHook(Integer hookId) throws GitLabApiException {
if (hookId == null) {
throw new RuntimeException("hookId cannot be null");
}
get(Response.Status.OK, null, "hooks", hookId);
}
}
package org.gitlab4j.api.systemhooks;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.gitlab4j.api.models.Visibility;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ProjectSystemHookEvent implements SystemHookEvent {
public static final String PROJECT_CREATE_EVENT = "project_create";
public static final String PROJECT_DESTROY_EVENT = "project_destroy";
public static final String PROJECT_RENAME_EVENT = "project_rename";
public static final String PROJECT_TRANSFER_EVENT = "project_transfer";
public static final String PROJECT_UPDATE_EVENT = "project_update";
private Date createdAt;
private Date updatedAt;
private String eventName;
private String name;
private String ownerEmail;
private String ownerName;
private String path;
private Integer projectId;
private String pathWithNamespace;
private Visibility projectVisibility;
private String oldPathWithNamespace;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
public String getEventName() {
return this.eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getOwnerEmail() {
return this.ownerEmail;
}
public void setOwnerEmail(String ownerEmail) {
this.ownerEmail = ownerEmail;
}
public String getOwnerName() {
return this.ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
public Integer getProjectId() {
return this.projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public String getPathWithNamespace() {
return pathWithNamespace;
}
public void setPathWithNamespace(String pathWithNamespace) {
this.pathWithNamespace = pathWithNamespace;
}
public Visibility getProjectVisibility() {
return projectVisibility;
}
public void setProjectVisibility(Visibility projectVisibility) {
this.projectVisibility = projectVisibility;
}
public String getOldPathWithNamespace() {
return oldPathWithNamespace;
}
public void setOldPathWithNamespace(String oldPathWithNamespace) {
this.oldPathWithNamespace = oldPathWithNamespace;
}
}
package org.gitlab4j.api.systemhooks;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.PROPERTY,
property="event_name")
@JsonSubTypes({
@JsonSubTypes.Type(value = ProjectSystemHookEvent.class, name = ProjectSystemHookEvent.PROJECT_CREATE_EVENT),
@JsonSubTypes.Type(value = ProjectSystemHookEvent.class, name = ProjectSystemHookEvent.PROJECT_DESTROY_EVENT),
@JsonSubTypes.Type(value = ProjectSystemHookEvent.class, name = ProjectSystemHookEvent.PROJECT_RENAME_EVENT),
@JsonSubTypes.Type(value = ProjectSystemHookEvent.class, name = ProjectSystemHookEvent.PROJECT_TRANSFER_EVENT),
@JsonSubTypes.Type(value = ProjectSystemHookEvent.class, name = ProjectSystemHookEvent.PROJECT_UPDATE_EVENT),
@JsonSubTypes.Type(value = TeamMemberSystemHookEvent.class, name = TeamMemberSystemHookEvent.NEW_TEAM_MEMBER_EVENT),
@JsonSubTypes.Type(value = TeamMemberSystemHookEvent.class, name = TeamMemberSystemHookEvent.TEAM_MEMBER_REMOVED_EVENT)
})
public interface SystemHookEvent {
public String getEventName();
}
package org.gitlab4j.api.systemhooks;
/**
* This class defines an event listener for the event fired when
* a System Hook notification has been received from a GitLab server.
*/
public interface SystemHookListener extends java.util.EventListener {
/**
* This method is called when a System Hook prject event has been received.
*
* @param event the ProjectSystemHookEvent instance
*/
public void onProjectEvent(ProjectSystemHookEvent event);
/**
* This method is called when a System Hook team member event has been received.
*
* @param event the TeamMemberSystemHookEvent instance containing info on the team member event
*/
public void onTeamMemberEvent(TeamMemberSystemHookEvent event);
}
package org.gitlab4j.api.systemhooks;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.HookManager;
import org.gitlab4j.api.utils.HttpRequestUtils;
import org.gitlab4j.api.utils.JacksonJson;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
/**
* This class provides a handler for processing GitLab System Hook callouts.
*/
public class SystemHookManager extends HookManager {
public static final String SYSTEM_HOOK_EVENT = "System Hook";
private final static Logger LOG = Logger.getLogger(SystemHookManager.class.getName());
private final JacksonJson jacksonJson = new JacksonJson();
// Collection of objects listening for System Hook events.
private final List<SystemHookListener> systemHookListeners = new CopyOnWriteArrayList<SystemHookListener>();
/**
* Create a HookManager to handle GitLab system hook events.
*/
public SystemHookManager() {
super();
}
/**
* Create a HookManager to handle GitLab system hook events which will be verified
* against the specified secretToken.
*
* @param secretToken the secret token to verify against
*/
public SystemHookManager(String secretToken) {
super(secretToken);
}
/**
* Parses and verifies an SystemHookEvent instance from the HTTP request and
* fires it off to the registered listeners.
*
* @param request the HttpServletRequest to read the Event instance from
* @throws GitLabApiException if the parsed event is not supported
*/
public void handleEvent(HttpServletRequest request) throws GitLabApiException {
if (!isValidSecretToken(request)) {
String message = "X-Gitlab-Token mismatch!";
LOG.warning(message);
throw new GitLabApiException(message);
}
String eventName = request.getHeader("X-Gitlab-Event");
LOG.info("handleEvent: X-Gitlab-Event=" + eventName);
if (!SYSTEM_HOOK_EVENT.equals(eventName)) {
String message = "Unsupported X-Gitlab-Event, event Name=" + eventName;
LOG.warning(message);
throw new GitLabApiException(message);
}
String errorMessage = null;
try {
SystemHookEvent event;
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(HttpRequestUtils.getShortRequestDump("System Hook", true, request));
String postData = HttpRequestUtils.getPostDataAsString(request);
LOG.fine("Raw POST data:\n" + postData);
event = jacksonJson.unmarshal(SystemHookEvent.class, postData);
LOG.fine(event.getEventName() + "\n" + jacksonJson.marshal(event) + "\n");
} else {
InputStreamReader reader = new InputStreamReader(request.getInputStream());
event = jacksonJson.unmarshal(SystemHookEvent.class, reader);
}
fireEvent(event);
} catch (JsonParseException jpe) {
errorMessage = jpe.getMessage();
LOG.warning("Error parsing JSON data, error=" + errorMessage);
} catch (JsonMappingException jme) {
errorMessage = jme.getMessage();
LOG.warning("Error mapping JSON data, error=" + errorMessage);
} catch (IOException ioe) {
errorMessage = ioe.getMessage();
LOG.warning("Error reading JSON data, error=" + errorMessage);
} catch (Exception e) {
errorMessage = e.getMessage();
LOG.warning("Unexpected error reading JSON data, error=" + errorMessage);
}
if (errorMessage != null)
throw new GitLabApiException(errorMessage);
}
/**
* Verifies the provided Event and fires it off to the registered listeners.
*
* @param event the Event instance to handle
* @throws GitLabApiException if the event is not supported
*/
public void handleEvent(SystemHookEvent event) throws GitLabApiException {
LOG.info("handleEvent: object_kind=" + event.getEventName());
fireEvent(event);
}
/**
* Adds a System Hook event listener.
*
* @param listener the SystemHookListener to add
*/
public void addListener(SystemHookListener listener) {
if (!systemHookListeners.contains(listener)) {
systemHookListeners.add(listener);
}
}
/**
* Removes a System Hook event listener.
*
* @param listener the SystemHookListener to remove
*/
public void removeListener(SystemHookListener listener) {
systemHookListeners.remove(listener);
}
/**
* Fire the event to the registered listeners.
*
* @param event the SystemHookEvent instance to fire to the registered event listeners
* @throws GitLabApiException if the event is not supported
*/
public void fireEvent(SystemHookEvent event) throws GitLabApiException {
if (event instanceof ProjectSystemHookEvent) {
fireProjectEvent((ProjectSystemHookEvent) event);
} else if (event instanceof TeamMemberSystemHookEvent) {
fireTeamMemberEvent((TeamMemberSystemHookEvent) event);
} else {
String message = "Unsupported event, event_named=" + event.getEventName();
LOG.warning(message);
throw new GitLabApiException(message);
}
}
protected void fireProjectEvent(ProjectSystemHookEvent event) {
for (SystemHookListener listener : systemHookListeners) {
listener.onProjectEvent(event);
}
}
protected void fireTeamMemberEvent(TeamMemberSystemHookEvent event) {
for (SystemHookListener listener : systemHookListeners) {
listener.onTeamMemberEvent(event);
}
}
}
package org.gitlab4j.api.systemhooks;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.gitlab4j.api.models.Visibility;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class TeamMemberSystemHookEvent implements SystemHookEvent {
public static final String NEW_TEAM_MEMBER_EVENT = "user_add_to_team";
public static final String TEAM_MEMBER_REMOVED_EVENT = "user_remove_from_team";
private Date createdAt;
private Date updatedAt;
private String eventName;
private String projectAccess;
private String projectName;
private String projectPath;
private Integer projectId;
private String projectPathWithNamespace;
private String userEmail;
private String userName;
private String userUsername;
private Integer userId;
private Visibility projectVisibility;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
public String getEventName() {
return this.eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public String getProjectAccess() {
return projectAccess;
}
public void setProjectAccess(String projectAccess) {
this.projectAccess = projectAccess;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectPath() {
return projectPath;
}
public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}
public Integer getProjectId() {
return projectId;
}
public void setProjectId(Integer projectId) {
this.projectId = projectId;
}
public String getProjectPathWithNamespace() {
return projectPathWithNamespace;
}
public void setProjectPathWithNamespace(String projectPathWithNamespace) {
this.projectPathWithNamespace = projectPathWithNamespace;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserUsername() {
return userUsername;
}
public void setUserUsername(String userUsername) {
this.userUsername = userUsername;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Visibility getProjectVisibility() {
return projectVisibility;
}
public void setProjectVisibility(Visibility projectVisibility) {
this.projectVisibility = projectVisibility;
}
public static String getNewTeamMemberEvent() {
return NEW_TEAM_MEMBER_EVENT;
}
public static String getTeamMemberRemovedEvent() {
return TEAM_MEMBER_REMOVED_EVENT;
}
}
package org.gitlab4j.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.util.List;
import org.gitlab4j.api.GitLabApi.ApiVersion;
import org.gitlab4j.api.models.SystemHook;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
/**
* In order for these tests to run you must set the following properties in test-gitlab4j.properties
*
* TEST_HOOK_URL
* TEST_HOST_URL
* TEST_PRIVATE_TOKEN
*
* If any of the above are NULL, all tests in this class will be skipped.
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestSystemHooksApi {
// The following needs to be set to your test repository
private static final String TEST_HOST_URL;
private static final String TEST_HOOK_URL;
private static final String TEST_PRIVATE_TOKEN;
static {
TEST_HOST_URL = TestUtils.getProperty("TEST_HOST_URL");
TEST_HOOK_URL = TestUtils.getProperty("TEST_HOOK_URL");
TEST_PRIVATE_TOKEN = TestUtils.getProperty("TEST_PRIVATE_TOKEN");
}
private static final String TEST_SECRET_TOKEN = "123456abcd";
private static GitLabApi gitLabApi;
public TestSystemHooksApi() {
super();
}
@BeforeClass
public static void setup() {
String problems = "";
if (TEST_HOOK_URL == null || TEST_HOOK_URL.trim().length() == 0) {
problems += "TEST_HOOK_URL cannot be empty\n";
}
if (TEST_HOST_URL == null || TEST_HOST_URL.trim().length() == 0) {
problems += "TEST_HOST_URL cannot be empty\n";
}
if (TEST_PRIVATE_TOKEN == null || TEST_PRIVATE_TOKEN.trim().length() == 0) {
problems += "TEST_PRIVATE_TOKEN cannot be empty\n";
}
if (problems.isEmpty()) {
gitLabApi = new GitLabApi(ApiVersion.V4, TEST_HOST_URL, TEST_PRIVATE_TOKEN);
} else {
System.err.print(problems);
}
}
@Before
public void beforeMethod() {
assumeTrue(gitLabApi != null);
}
@Test
public void testAddSystemHook() throws GitLabApiException {
SystemHook hook = gitLabApi.getSystemHooksApi().addSystemHook(TEST_HOOK_URL, TEST_SECRET_TOKEN, true, false, true);
assertNotNull(hook);
assertEquals(TEST_HOOK_URL, hook.getUrl());
assertTrue(hook.getPushEvents());
assertFalse(hook.getTagPushEvents());
assertTrue(hook.getEnableSslVerification());
gitLabApi.getSystemHooksApi().deleteSystemHook(hook);
}
@Test
public void testGerSystemHooks() throws GitLabApiException {
SystemHook hook = gitLabApi.getSystemHooksApi().addSystemHook(TEST_HOOK_URL, TEST_SECRET_TOKEN, true, false, true);
assertNotNull(hook);
List<SystemHook> hooks = gitLabApi.getSystemHooksApi().getSystemHooks();
assertNotNull(hooks);
assertFalse(hooks.isEmpty());
gitLabApi.getSystemHooksApi().deleteSystemHook(hook);
}
}
{
"created_at": "2012-07-21T07:30:54Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_create",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
"owner_name": "John Smith",
"path": "storecloud",
"path_with_namespace": "jsmith/storecloud",
"project_id": 74,
"project_visibility": "private"
}
\ No newline at end of file
{
"created_at": "2012-07-21T07:30:56Z",
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_team",
"project_access": "Master",
"project_id": 74,
"project_name": "StoreCloud",
"project_path": "storecloud",
"project_path_with_namespace": "jsmith/storecloud",
"user_email": "johnsmith@gmail.com",
"user_name": "John Smith",
"user_username": "johnsmith",
"user_id": 41,
"project_visibility": "private"
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment