Skip to content

Commit

Permalink
feat: backend changes to implement short codes in webhooks to pass dy…
Browse files Browse the repository at this point in the history
…namic info on webhook execution (#7623)

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

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

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

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* feat: removed shortCode cloumn from features table

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* feat: adding webhook permissions in admin-ui configuration

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* feat: correct sonar code smells

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* feat: correct sonar code smells

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* feat: add datatypes to table columns

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* fix: error in triggrering webhook

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* fix: remove commented code

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

* fix: removing compilation error

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>

---------

Signed-off-by: Arnab Dutta <arnab.bdutta@gmail.com>
Co-authored-by: YuriyZ <yzabrovarniy@gmail.com>
  • Loading branch information
duttarnab and yuriyz committed Feb 26, 2024
1 parent 6c1582d commit 4a725ff
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 134 deletions.
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

0 comments on commit 4a725ff

Please sign in to comment.