Skip to content

Commit

Permalink
control-service: enable webhooks authentication
Browse files Browse the repository at this point in the history
Signed-off-by: Miroslav Ivanov [email protected]
  • Loading branch information
mivanov1988 committed Aug 15, 2023
1 parent bf0eab0 commit ff23116
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,11 @@ webHooks:
postCreate:
webhookUri: ""
internalErrorsRetries: 3
authenticationEnabled: false
postDelete:
webhookUri: ""
internalErrorsRetries: 3
authenticationEnabled: false

### Security configuration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,15 @@ dependencies { // Implementation dependencies are found on compile classpath of
implementation versions.'com.amazonaws:aws-java-sdk-core'
implementation versions.'com.amazonaws:aws-java-sdk-sts'

implementation 'org.springframework.security:spring-security-oauth2-jose'

compileOnly versions.'org.hibernate:hibernate-jpamodelgen'
annotationProcessor versions.'org.hibernate:hibernate-jpamodelgen'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation versions.'com.mmnaseri.utils:spring-data-mock'
testImplementation versions.'org.mock-server:mockserver-netty'
testImplementation 'org.springframework.security:spring-security-oauth2-jose'
testImplementation versions.'org.mockito:mockito-core'
testImplementation versions.'net.bytebuddy:byte-buddy'
testImplementation versions.'org.testcontainers:testcontainers'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Optional;

/**
* AuthorizationProvider class used in {@link AuthorizationInterceptor} responsible for handling of
Expand Down Expand Up @@ -84,6 +87,20 @@ public String getUserId(Authentication authentication) {
}
}

public String getAccessToken() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String accessToken = null;

if (authentication instanceof JwtAuthenticationToken) {
JwtAuthenticationToken oauthToken = (JwtAuthenticationToken) authentication;
accessToken = Optional.ofNullable(oauthToken.getToken())
.map(AbstractOAuth2Token::getTokenValue)
.orElse(null);
}

return accessToken;
}

String parsePropertyFromURI(String contextPath, String fullPath, int index) {
String uri = URLDecoder.decode(fullPath, Charset.defaultCharset());
if (!StringUtils.isBlank(contextPath)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package com.vmware.taurus.authorization.webhook;

import com.vmware.taurus.authorization.AuthorizationInterceptor;
import com.vmware.taurus.authorization.provider.AuthorizationProvider;
import com.vmware.taurus.base.FeatureFlags;
import com.vmware.taurus.exception.AuthorizationError;
import com.vmware.taurus.exception.ExternalSystemError;
import com.vmware.taurus.service.webhook.WebHookRequestBody;
Expand All @@ -15,6 +17,7 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
* AuthorizationWebhookProvider class which delegates authorization request to a third party webhook
Expand All @@ -31,13 +34,17 @@ public class AuthorizationWebHookProvider extends WebHookService<AuthorizationBo
public AuthorizationWebHookProvider(
@Value("${datajobs.authorization.webhook.endpoint}") String webHookEndpoint,
@Value("${datajobs.authorization.webhook.internal.errors.retries:1}")
int retriesOn5xxErrors) {
super(webHookEndpoint, retriesOn5xxErrors, log);
int retriesOn5xxErrors,
@Value("${datajobs.authorization.webhook.authentication.enabled:false}") boolean authenticationEnabled,
RestTemplate restTemplate,
FeatureFlags featureFlags,
AuthorizationProvider authorizationProvider) {
super(webHookEndpoint, retriesOn5xxErrors, authenticationEnabled, log, restTemplate, featureFlags, authorizationProvider);
}

@Override
public void ensureConfigured() {
if (StringUtils.isBlank(getWebHookEndpoint())) {
if (StringUtils.isBlank(webHookEndpoint)) {
throw new AuthorizationError(
"Authorization webhook endpoint is not configured",
"Cannot determine whether a user is authorized to do this request",
Expand All @@ -48,7 +55,7 @@ public void ensureConfigured() {

@Override
protected String getWebHookRequestURL(AuthorizationBody webHookRequestBody) {
return getWebHookEndpoint();
return webHookEndpoint;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

package com.vmware.taurus.datajobs.webhook;

import com.vmware.taurus.authorization.provider.AuthorizationProvider;
import com.vmware.taurus.base.FeatureFlags;
import com.vmware.taurus.service.webhook.WebHookRequestBody;
import com.vmware.taurus.service.webhook.WebHookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
* PostCreateWebHookProvider class which delegates custom post data job creation operations via a
Expand All @@ -24,7 +27,11 @@ public class PostCreateWebHookProvider extends WebHookService<WebHookRequestBody

public PostCreateWebHookProvider(
@Value("${datajobs.post.create.webhook.endpoint}") String webHookEndpoint,
@Value("${datajobs.post.create.webhook.internal.errors.retries:-1}") int retriesOn5xxErrors) {
super(webHookEndpoint, retriesOn5xxErrors, log);
@Value("${datajobs.post.create.webhook.internal.errors.retries:-1}") int retriesOn5xxErrors,
@Value("${datajobs.post.create.webhook.authentication.enabled:false}") boolean authenticationEnabled,
RestTemplate restTemplate,
FeatureFlags featureFlags,
AuthorizationProvider authorizationProvider) {
super(webHookEndpoint, retriesOn5xxErrors, authenticationEnabled, log, restTemplate, featureFlags, authorizationProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@

package com.vmware.taurus.datajobs.webhook;

import com.vmware.taurus.authorization.provider.AuthorizationProvider;
import com.vmware.taurus.base.FeatureFlags;
import com.vmware.taurus.service.webhook.WebHookRequestBody;
import com.vmware.taurus.service.webhook.WebHookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
* PostDeleteWebHookProvider class which delegates custom post data job delete operations via a
Expand All @@ -21,9 +25,14 @@
@Service
@Slf4j
public class PostDeleteWebHookProvider extends WebHookService<WebHookRequestBody> {

public PostDeleteWebHookProvider(
@Value("${datajobs.post.delete.webhook.endpoint}") String webHookEndpoint,
@Value("${datajobs.post.delete.webhook.internal.errors.retries:-1}") int retriesOn5xxErrors) {
super(webHookEndpoint, retriesOn5xxErrors, log);
@Value("${datajobs.post.delete.webhook.internal.errors.retries:-1}") int retriesOn5xxErrors,
@Value("${datajobs.post.delete.webhook.authentication.enabled:false}") boolean authenticationEnabled,
RestTemplate restTemplate,
FeatureFlags featureFlags,
AuthorizationProvider authorizationProvider) {
super(webHookEndpoint, retriesOn5xxErrors, authenticationEnabled, log, restTemplate, featureFlags, authorizationProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@

package com.vmware.taurus.service.webhook;

import com.vmware.taurus.authorization.provider.AuthorizationProvider;
import com.vmware.taurus.base.FeatureFlags;
import com.vmware.taurus.exception.ExternalSystemError;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.http.*;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;

Expand All @@ -33,44 +29,49 @@
* @see com.vmware.taurus.datajobs.webhook.PostCreateWebHookProvider
* @see com.vmware.taurus.authorization.webhook.AuthorizationWebHookProvider
*/
@Service
@RequiredArgsConstructor
public abstract class WebHookService<T extends WebHookRequestBody> implements InitializingBean {

@Getter private final String webHookEndpoint;
protected final String webHookEndpoint;

private final int retriesOn5xxErrors;

private final boolean authenticationEnabled;

private final Logger log;

@Autowired private RestTemplate restTemplate;
private final RestTemplate restTemplate;

private final FeatureFlags featureFlags;

private final AuthorizationProvider authorizationProvider;

public Optional<WebHookResult> invokeWebHook(T webHookRequestBody) {
ensureConfigured();

if (StringUtils.isBlank(getWebHookEndpoint())) {
if (StringUtils.isBlank(webHookEndpoint)) {
log.debug("The webHook Endpoint is not configured. Requests will not be send ...");
return Optional.empty();
}

ResponseEntity responseEntity = sendRequest(webHookRequestBody);

if (responseEntity.getStatusCode().is5xxServerError()) {
log.debug("The WebHook invocation {} returns 5xxServerError ...", getWebHookEndpoint());
log.debug("The WebHook invocation {} returns 5xxServerError ...", webHookEndpoint);
responseEntity = retry5xxWebHookRequest(webHookRequestBody, responseEntity);
}
if (responseEntity.getStatusCode().is4xxClientError()) {
log.debug("The WebHook invocation {} returns 4xxClientError ...", getWebHookEndpoint());
log.debug("The WebHook invocation {} returns 4xxClientError ...", webHookEndpoint);
return Optional.of(provideClientErrorMessage(responseEntity));
}
if (responseEntity.getStatusCode().is2xxSuccessful()) {
log.debug("The WebHook invocation {} is successful ...", getWebHookEndpoint());
log.debug("The WebHook invocation {} is successful ...", webHookEndpoint);
return Optional.of(provideSuccessMessage(responseEntity));
}

log.debug(
"The WebHook invocation {} returns unhandled status code: {}",
getWebHookEndpoint(),
webHookEndpoint,
responseEntity.getStatusCode().value());
ExternalSystemError.MainExternalSystem mainExternalSystem = getExternalSystemType();
throw new ExternalSystemError(
Expand Down Expand Up @@ -103,7 +104,8 @@ private ResponseEntity sendRequest(T webHookRequestBody) {
// necessary
log.info("WebHook body: {}", webHookRequestBody.toString());
try {
HttpEntity<WebHookRequestBody> request = new HttpEntity<>(webHookRequestBody);
HttpEntity<WebHookRequestBody> request = createHttpRequest(webHookRequestBody);

return restTemplate.exchange(
getWebHookRequestURL(webHookRequestBody), HttpMethod.POST, request, String.class);
} catch (RestClientResponseException responseException) {
Expand Down Expand Up @@ -133,7 +135,7 @@ protected void ensureConfigured() {}
* @return a valid URL
*/
protected String getWebHookRequestURL(T webHookRequestBody) {
return getWebHookEndpoint() + webHookRequestBody.getRequestedHttpPath();
return webHookEndpoint + webHookRequestBody.getRequestedHttpPath();
}

/**
Expand Down Expand Up @@ -175,4 +177,21 @@ protected WebHookResult provideSuccessMessage(ResponseEntity responseEntity) {
.success(true)
.build();
}

private HttpEntity<WebHookRequestBody> createHttpRequest(T webHookRequestBody) {
HttpEntity<WebHookRequestBody> request = null;

if (featureFlags.isSecurityEnabled() && authenticationEnabled) {
String accessToken = authorizationProvider.getAccessToken();

if (StringUtils.isNotEmpty(accessToken)) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken);

request = new HttpEntity<>(webHookRequestBody, httpHeaders);
}
}

return request != null ? request : new HttpEntity<>(webHookRequestBody);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ datajobs.authorization.jwt.claim.username=username
# Data Jobs post webhook settings (Create and Delete)
datajobs.post.create.webhook.endpoint=
datajobs.post.create.webhook.internal.errors.retries=3
datajobs.post.create.webhook.authentication.enabled=false
datajobs.post.delete.webhook.endpoint=
datajobs.post.delete.webhook.internal.errors.retries=3
datajobs.post.delete.webhook.authentication.enabled=false

# The owner name and email address that will be used to send all Versatile Data Kit related email notifications.
datajobs.notification.owner.email=[email protected]
Expand Down
Loading

0 comments on commit ff23116

Please sign in to comment.