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

feat: backend changes to implement short codes in webhooks to pass dynamic info on webhook execution #7623

Merged
merged 13 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import java.util.List;
import java.util.Objects;

@DataEntry(sortBy = { "auiFeatureId" })
@DataEntry(sortBy = {"auiFeatureId"})
@ObjectClass(value = "auiFeatures")
public class AuiFeature extends Entry implements Serializable {

Expand Down Expand Up @@ -53,7 +53,7 @@ public List<String> getWebhookIdsMapped() {
}

public void setWebhookIdsMapped(List<String> webhookIdsMapped) {
if(webhookIdsMapped != null) {
if (webhookIdsMapped != null) {
this.webhookIdsMapped = Lists.newArrayList(Sets.newHashSet(webhookIdsMapped));
}
}
Expand All @@ -64,7 +64,7 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
AuiFeature that = (AuiFeature) o;
return auiFeatureId.equals(that.auiFeatureId) && displayName.equals(that.displayName) && jansScope.equals(that.jansScope) && Objects.equals(webhookIdsMapped, that.webhookIdsMapped);
return auiFeatureId.equals(that.auiFeatureId) && displayName.equals(that.displayName) && jansScope.equals(that.jansScope);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.jans.ca.plugin.adminui.model.webhook;

import io.jans.orm.annotation.JsonObject;

import java.io.Serializable;
import java.util.Map;

public class ShortCodeRequest implements Serializable {
private String webhookId;
@JsonObject
transient Map<String, Object> shortcodeValueMap;

public String getWebhookId() {
return webhookId;
}

public void setWebhookId(String webhookId) {
this.webhookId = webhookId;
}

public Map<String, Object> getShortcodeValueMap() {
return shortcodeValueMap;
}

public void setShortcodeValueMap(Map<String, Object> shortcodeValueMap) {
this.shortcodeValueMap = shortcodeValueMap;
}

@Override
public String toString() {
return "ShortCodeRequest{" +
"webhookId='" + webhookId + '\'' +
", shortcodeValueMap=" + shortcodeValueMap +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class WebhookEntry extends Entry implements Serializable {
private String url;
@AttributeName(name = "httpRequestBody")
@JsonObject
private Map<String, String> httpRequestBody;
private transient Map<String, Object> httpRequestBody;
@NotNull
@AttributeName(name = "httpMethod")
private String httpMethod;
Expand Down Expand Up @@ -125,14 +125,14 @@ public void setDescription(String description) {
this.description = description;
}

public Map<String, String> getHttpRequestBody() {
public Map<String, Object> getHttpRequestBody() {
if (httpRequestBody == null) {
httpRequestBody = new HashMap<>();
}
return httpRequestBody;
}

public void setHttpRequestBody(Map<String, String> httpRequestBody) {
public void setHttpRequestBody(Map<String, Object> httpRequestBody) {
this.httpRequestBody = httpRequestBody;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.jans.ca.plugin.adminui.model.auth.GenericResponse;
import io.jans.ca.plugin.adminui.model.exception.ApplicationException;
import io.jans.ca.plugin.adminui.model.webhook.AuiFeature;
import io.jans.ca.plugin.adminui.model.webhook.ShortCodeRequest;
import io.jans.ca.plugin.adminui.model.webhook.WebhookEntry;
import io.jans.ca.plugin.adminui.service.webhook.WebhookService;
import io.jans.ca.plugin.adminui.utils.AppConstants;
Expand All @@ -29,6 +30,7 @@
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.commons.collections.CollectionUtils;
import org.python.google.common.collect.Sets;
import org.slf4j.Logger;

Expand Down Expand Up @@ -311,34 +313,36 @@ public Response deleteWebhook(@Parameter(description = "Webhook identifier") @Pa

@Operation(summary = "Trigger webhooks mapped to featureId", description = "Trigger webhooks mapped to featureId", operationId = "trigger-webhook", tags = {
"Admin UI - Webhooks"}, security = @SecurityRequirement(name = "oauth2", scopes = {SCOPE_WEBHOOK_READ}))
@RequestBody(description = "Webhook object", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = ShortCodeRequest.class), examples = @ExampleObject(name = "Request json example", value = "example/webhook/trigger-webooks-request.json")))
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = AuiFeature.class), examples = @ExampleObject(name = "Response json example", value = "example/webhook/trigger-webooks.json"))),
@ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = AuiFeature.class), examples = @ExampleObject(name = "Response json example", value = "example/webhook/trigger-webooks-response.json"))),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = GenericResponse.class, description = "License response"))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "Not Found"),
@ApiResponse(responseCode = "500", description = "InternalServerError", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = GenericResponse.class, description = "License response")))})
@GET
@POST
@Path(TRIGGER_PATH + FEATURE_ID_PATH_VARIABLE)
@ProtectedApi(scopes = {SCOPE_WEBHOOK_READ})
@Produces(MediaType.APPLICATION_JSON)
public Response triggerWebhook(@Parameter(description = "Admin UI feature identifier") @PathParam(AppConstants.ADMIN_UI_FEATURE_ID) @NotNull String featureId) {
public Response triggerWebhook(@Parameter(description = "Admin UI feature identifier") @PathParam(AppConstants.ADMIN_UI_FEATURE_ID) @NotNull String featureId,
@Valid @NotNull List<ShortCodeRequest> shortCodes) {
try {
if (log.isDebugEnabled()) {
log.debug("Triggering all webhooks for Admin UI feature - featureId: {}", escapeLog(featureId));
}
HashSet<String> featureIdSet = Sets.newHashSet();
featureIdSet.add(featureId);
List<AuiFeature> featureList = webhookService.getAuiFeaturesByIds(featureIdSet);
if (CommonUtils.isEmptyOrNullCollection(featureList)) {
if (CollectionUtils.isEmpty(featureList)) {
log.error(ErrorResponse.WEBHOOK_RECORD_NOT_EXIST.getDescription());
throw new ApplicationException(Response.Status.BAD_REQUEST.getStatusCode(), ErrorResponse.WEBHOOK_RECORD_NOT_EXIST.getDescription());
}
AuiFeature featureObj = featureList.get(0);
if (CommonUtils.isEmptyOrNullCollection(featureObj.getWebhookIdsMapped())) {
if (CollectionUtils.isEmpty(featureObj.getWebhookIdsMapped())) {
log.error(ErrorResponse.NO_WEBHOOK_FOUND.getDescription());
throw new ApplicationException(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ErrorResponse.NO_WEBHOOK_FOUND.getDescription());
}
List<GenericResponse> responseList = webhookService.triggerEnabledWebhooks(Sets.newHashSet(featureObj.getWebhookIdsMapped()));
List<GenericResponse> responseList = webhookService.triggerEnabledWebhooks(Sets.newHashSet(featureObj.getWebhookIdsMapped()), shortCodes);

return Response.ok(responseList).build();
} catch (ApplicationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public AdminRole getRoleObjByName(String role) throws ApplicationException {
try {
AdminConf adminConf = entryManager.find(AdminConf.class, AppConstants.ADMIN_UI_CONFIG_DN);
List<AdminRole> roles = adminConf.getDynamic().getRoles().stream().filter(ele -> ele.getRole().equals(role)).collect(Collectors.toList());
if (!CommonUtils.isEmptyOrNullCollection(roles)) {
if (!CollectionUtils.isEmpty(roles)) {
return roles.get(0);
}
log.error(ErrorResponse.ROLE_NOT_FOUND.getDescription());
Expand Down Expand Up @@ -208,7 +208,7 @@ public AdminPermission getPermissionObjByName(String permission) throws Applicat
try {
AdminConf adminConf = entryManager.find(AdminConf.class, AppConstants.ADMIN_UI_CONFIG_DN);
List<AdminPermission> permissions = adminConf.getDynamic().getPermissions().stream().filter(ele -> ele.getPermission().equals(permission)).collect(Collectors.toList());
if (!CommonUtils.isEmptyOrNullCollection(permissions)) {
if (!CollectionUtils.isEmpty(permissions)) {
return permissions.get(0);
}
log.error(ErrorResponse.ROLE_NOT_FOUND.getDescription());
Expand Down Expand Up @@ -385,7 +385,7 @@ public RolePermissionMapping getAdminUIRolePermissionsMapping(String role) throw
List<RolePermissionMapping> roleScopeMappings = adminConf.getDynamic().getRolePermissionMapping()
.stream().filter(ele -> ele.getRole().equalsIgnoreCase(role))
.collect(Collectors.toList());
if (!CommonUtils.isEmptyOrNullCollection(roleScopeMappings)) {
if (!CollectionUtils.isEmpty(roleScopeMappings)) {
return roleScopeMappings.get(0);
}
log.error(ErrorResponse.ROLE_PERMISSION_MAP_NOT_FOUND.getDescription());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.jans.ca.plugin.adminui.model.auth.GenericResponse;
import io.jans.ca.plugin.adminui.model.exception.ApplicationException;
import io.jans.ca.plugin.adminui.model.webhook.WebhookEntry;
import io.jans.ca.plugin.adminui.utils.AppConstants;
import io.jans.ca.plugin.adminui.utils.ClientFactory;
import io.jans.ca.plugin.adminui.utils.CommonUtils;
import io.jans.ca.plugin.adminui.utils.ErrorResponse;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;

import java.util.HashMap;
Expand All @@ -32,36 +35,52 @@ public WebhookCallable(WebhookEntry webhook, Logger log) {

@Override
public GenericResponse call() throws ApplicationException {
log.debug("Webhook processing started. Id: {}. Name: {}, URL : {}, HttpMethod: {}", webhook.getWebhookId(), webhook.getDisplayName(), webhook.getUrl(), webhook.getHttpMethod());
Invocation.Builder request = ClientFactory.instance().getClientBuilder(webhook.getUrl());
//getting all headers
webhook.getHttpHeaders().stream()
.filter(Objects::nonNull)
.forEach(header -> request.header(header.getKey(), header.getValue()));
//Call rest endpoint
Invocation invocation = checkHttpMethod(request);
if (invocation == null) {
log.error("Error in creating invocation object for rest call (Name: {}, Id: {})", webhook.getDisplayName(), webhook.getWebhookId());
return CommonUtils.createGenericResponse(false,
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"Error in creating invocation object for rest call (Name: " + webhook.getDisplayName() + ", Id: " + webhook.getWebhookId() + ")");
}
Response response = invocation.invoke();
ObjectMapper objectMapper = new ObjectMapper();
log.debug("Webhook (Name: {}, Id: {}) response status code: {}", webhook.getDisplayName(), webhook.getWebhookId(), response.getStatus());
JsonNode jsonNode = objectMapper.createObjectNode();
((ObjectNode) jsonNode).put("webhookId", webhook.getWebhookId());
((ObjectNode) jsonNode).put("webhookName", webhook.getDisplayName());
if (response.getStatus() == Response.Status.OK.getStatusCode() ||
response.getStatus() == Response.Status.CREATED.getStatusCode() ||
response.getStatus() == Response.Status.ACCEPTED.getStatusCode()) {
String responseData = response.readEntity(String.class);
log.debug("Webhook (Name: {}, Id: {}) responseData : {}", webhook.getDisplayName(), webhook.getWebhookId(), responseData);
return CommonUtils.createGenericResponse(true, response.getStatus(), responseData, jsonNode);
} else {
String responseData = response.readEntity(String.class);
log.error("Webhook (Name: {}, Id: {}) responseData : {}", webhook.getDisplayName(), webhook.getWebhookId(), responseData);
return CommonUtils.createGenericResponse(false, response.getStatus(), responseData, jsonNode);
try {
log.debug("Webhook processing started. Id: {}. Name: {}, URL : {}, HttpMethod: {}", webhook.getWebhookId(), webhook.getDisplayName(), webhook.getUrl(), webhook.getHttpMethod());
Invocation.Builder request = ClientFactory.instance().getClientBuilder(webhook.getUrl());
//getting all headers
webhook.getHttpHeaders().stream()
.filter(Objects::nonNull)
.forEach(header -> request.header(header.getKey(), header.getValue()));
//Call rest endpoint
Invocation invocation = checkHttpMethod(request);
if (invocation == null) {
log.error("Error in creating invocation object for rest call (Name: {}, Id: {})", webhook.getDisplayName(), webhook.getWebhookId());
return CommonUtils.createGenericResponse(false,
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
"Error in creating invocation object for rest call (Name: " + webhook.getDisplayName() + ", Id: " + webhook.getWebhookId() + ")");
}
Response response = invocation.invoke();
log.debug("Webhook (Name: {}, Id: {}) response status code: {}", webhook.getDisplayName(), webhook.getWebhookId(), response.getStatus());

((ObjectNode) jsonNode).put("webhookId", webhook.getWebhookId());
((ObjectNode) jsonNode).put("webhookName", webhook.getDisplayName());
((ObjectNode) jsonNode).put("webhookMethod", webhook.getHttpMethod());
if (Lists.newArrayList("POST", "PUT", "PATCH").contains(webhook.getHttpMethod())) {
((ObjectNode) jsonNode).put("webhookRequestBody", webhook.getHttpRequestBody().toString());
}
if (response.getStatus() == Response.Status.OK.getStatusCode() ||
response.getStatus() == Response.Status.CREATED.getStatusCode() ||
response.getStatus() == Response.Status.ACCEPTED.getStatusCode()) {
String responseData = response.readEntity(String.class);
log.debug("Webhook (Name: {}, Id: {}) responseData : {}", webhook.getDisplayName(), webhook.getWebhookId(), responseData);
return CommonUtils.createGenericResponse(true, response.getStatus(), responseData, jsonNode);
} else {
String responseData = response.readEntity(String.class);
log.error("Webhook (Name: {}, Id: {}) responseData : {}", webhook.getDisplayName(), webhook.getWebhookId(), responseData);
return CommonUtils.createGenericResponse(false, response.getStatus(), responseData, jsonNode);
}
} catch (Exception e) {
log.error(ErrorResponse.WEBHOOK_TRIGGER_ERROR.getDescription(), e);
((ObjectNode) jsonNode).put("webhookId", webhook.getWebhookId());
((ObjectNode) jsonNode).put("webhookName", webhook.getDisplayName());
((ObjectNode) jsonNode).put("webhookMethod", webhook.getHttpMethod());
if (Lists.newArrayList("POST", "PUT", "PATCH").contains(webhook.getHttpMethod())) {
((ObjectNode) jsonNode).put("webhookRequestBody", webhook.getHttpRequestBody().toString());
}
return CommonUtils.createGenericResponse(false, Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e.getMessage(), jsonNode);
}
}

Expand All @@ -77,7 +96,7 @@ private Invocation checkHttpMethod(Invocation.Builder request) {
case "POST":
case "PUT":
case "PATCH":
if (CommonUtils.isEmptyOrNullCollection(webhook.getHttpRequestBody())) {
if (MapUtils.isEmpty(webhook.getHttpRequestBody())) {
break;
}
Map<String, Object> requestBody = setRequestBody(webhook);
Expand All @@ -95,21 +114,16 @@ private Invocation checkHttpMethod(Invocation.Builder request) {
private Map<String, Object> setRequestBody(WebhookEntry webhook) {
try {
Map<String, Object> body = new HashMap<>();
webhook.getHttpHeaders().stream()
.filter(Objects::nonNull)
.forEach(header -> {
if (header.getKey().equalsIgnoreCase(AppConstants.CONTENT_TYPE) && header.getKey().equalsIgnoreCase(AppConstants.APPLICATION_JSON)) {
Map<String, String> reqBody = webhook.getHttpRequestBody();
for (String key : reqBody.keySet()) {
body.put(key, reqBody.get(key));
}
}
});
if (webhook.getHttpHeaders().stream().anyMatch(header -> header.getKey().equals(AppConstants.CONTENT_TYPE))) {
Map<String, Object> reqBody = webhook.getHttpRequestBody();
for (String key : reqBody.keySet()) {
body.put(key, reqBody.get(key));
}
}
return body;
} catch (Exception ex) {
log.error("Error in parsing request-body.", ex);
return Maps.newHashMap();
}
}

}
Loading