Commit 77d83e61 authored by Nikhil Singh[MicroSoft]'s avatar Nikhil Singh[MicroSoft]
Browse files

Commit 2 contents:

1-Unit Tests
2-Review Comments
3-Hmac/gsa abstraction
parent 6931d51f
Pipeline #46464 failed with stages
in 2 minutes and 9 seconds
......@@ -16,6 +16,8 @@
package org.opengroup.osdu.notification.api;
import org.opengroup.osdu.core.common.http.HttpResponse;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor;
import org.opengroup.osdu.notification.service.interfaces.INotificationHandler;
import org.opengroup.osdu.notification.utils.Config;
......@@ -28,14 +30,19 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.annotation.RequestScope;
import java.util.Map;
@RestController
@RequestScope
@RequestMapping("/push-handlers")
public class PubsubEndpoint {
@Autowired
private IPubsubRequestBodyExtractor pubsubRequestBodyExtractor;
@Autowired
private INotificationHandler notificationHandler;
@Autowired
private JaxRsDpsLog log;
private final String ACKNOWLEDGE = "message acknowledged by client";
private final String NOT_ACKNOWLEDGE = "message not acknowledged by client";
@PostMapping("/records-changed")
@PreAuthorize("@authorizationFilter.hasAnyPermission('" + Config.OPS + "', '" + Config.PUBSUB + "')")
......@@ -43,6 +50,17 @@ public class PubsubEndpoint {
String notificationId = this.pubsubRequestBodyExtractor.extractNotificationIdFromRequestBody();
String pubsubMessage = this.pubsubRequestBodyExtractor.extractDataFromRequestBody();
Map<String, String> headerAttributes = this.pubsubRequestBodyExtractor.extractAttributesFromRequestBody();
return notificationHandler.NotifySubscriber(notificationId,pubsubMessage,headerAttributes);
try {
HttpResponse response = notificationHandler.NotifySubscriber(notificationId, pubsubMessage, headerAttributes);
if (!response.isSuccessCode()) {
this.log.error(NOT_ACKNOWLEDGE);
return ResponseEntity.badRequest().body(NOT_ACKNOWLEDGE);
}
this.log.info(ACKNOWLEDGE);
return ResponseEntity.ok(ACKNOWLEDGE);
}catch(Exception e)
{ this.log.error("An exception occurred: " + e );
return ResponseEntity.badRequest().body(NOT_ACKNOWLEDGE);
}
}
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.auth;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.opengroup.osdu.core.common.model.notification.GsaSecret;
import org.opengroup.osdu.core.common.model.notification.GsaSecretValue;
import org.opengroup.osdu.core.common.model.notification.Secret;
import org.opengroup.osdu.notification.auth.interfaces.SecretAuth;
import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class GsaAuth implements SecretAuth {
@Autowired
private IGoogleServiceAccount gsaTokenProvider;
private GsaSecret gsaSecret;
@Override
public void setSecret(Secret secret) {
this.gsaSecret = (GsaSecret) secret;
}
@Override
public String getPushUrl(String endpoint) {
return endpoint;
}
@Override
public void getRequestHeaders(Map<String, String> requestHeader) {
GsaSecretValue gsaSecretValue = gsaSecret.getValue();
JsonParser jsonParser = new JsonParser();
JsonElement root = jsonParser.parse(gsaSecretValue.getKey());
String keyString = root.getAsJsonObject().toString();
String idToken = this.gsaTokenProvider.getIdToken(keyString, gsaSecretValue.getAudience());
requestHeader.put("Authorization", idToken);
}
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.auth;
import org.opengroup.osdu.core.common.cryptographic.ISignatureService;
import org.opengroup.osdu.core.common.model.notification.HmacSecret;
import org.opengroup.osdu.core.common.model.notification.Secret;
import org.opengroup.osdu.notification.auth.interfaces.SecretAuth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class HmacAuth implements SecretAuth {
@Autowired
private ISignatureService signatureService;
private HmacSecret hmacSecret;
@Override
public void setSecret(Secret secret) {
this.hmacSecret = (HmacSecret) secret;
}
@Override
public String getPushUrl(String endpoint) {
String pushUrl = endpoint;
try {
String signedjwt = this.signatureService.getSignedSignature(endpoint, hmacSecret.getValue());
pushUrl += "?hmac=" + signedjwt;
}catch(Exception e) {
System.out.println("An exception occurred in signature service: " + e);
}
return pushUrl ;
}
@Override
public void getRequestHeaders(Map<String, String> requestHeader) {
// No Op
}
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.auth.factory;
import org.opengroup.osdu.core.common.model.notification.Secret;
import org.opengroup.osdu.notification.auth.GsaAuth;
import org.opengroup.osdu.notification.auth.HmacAuth;
import org.opengroup.osdu.notification.auth.interfaces.SecretAuth;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AuthFactory {
@Autowired
private HmacAuth hmacAuth;
@Autowired
private GsaAuth gsaAuth;
public SecretAuth getSecret(String secretType) {
switch(secretType) {
case "HMAC": return hmacAuth;
case "GSA": return gsaAuth;
}
return null;
}
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.auth.interfaces;
import org.opengroup.osdu.core.common.model.notification.Secret;
import java.util.Map;
public interface SecretAuth {
String getPushUrl(String endpoint);
void setSecret(Secret secret);
void getRequestHeaders(Map<String, String> requestHeader);
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.service;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.opengroup.osdu.core.common.cryptographic.ISignatureService;
import org.opengroup.osdu.core.common.http.HttpClient;
import org.opengroup.osdu.core.common.http.HttpRequest;
import org.opengroup.osdu.core.common.http.HttpResponse;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.notification.*;
import org.opengroup.osdu.core.common.notification.ISubscriptionFactory;
import org.opengroup.osdu.notification.di.SubscriptionCacheFactory;
import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount;
import org.opengroup.osdu.notification.auth.factory.AuthFactory;
import org.opengroup.osdu.notification.auth.interfaces.SecretAuth;
import org.opengroup.osdu.notification.service.interfaces.INotificationHandler;
import org.opengroup.osdu.notification.service.interfaces.ISubscriptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class NotificationHandler implements INotificationHandler {
@Autowired
private SubscriptionCacheFactory subscriptionCacheFactory;
@Autowired
private ISubscriptionFactory subscriptionFactory;
@Autowired
private ISignatureService signatureService;
@Autowired
private IGoogleServiceAccount gsaTokenProvider;
@Autowired
private JaxRsDpsLog log;
@Autowired
private HttpClient httpClient;
@Autowired
private ISubscriptionHandler subscriptionHandler;
private static final String HMAC_TYPE = "HMAC";
private static final String GSA_TYPE = "GSA";
private final int WAITING_TIME = 30000;
private final String ACKNOWLEDGE = "message acknowledged by client";
private final String NOT_ACKNOWLEDGE = "message not acknowledged by client";
@Autowired
private AuthFactory authFactory;
public ResponseEntity NotifySubscriber(String notificationId, String pubsubMessage, Map<String, String> headerAttributes) {
try {
private final int WAITING_TIME = 30000;
public HttpResponse NotifySubscriber (String notificationId, String pubsubMessage, Map<String, String> headerAttributes) throws Exception{
Subscription subscription = subscriptionHandler.getSubscriptionFromCache(notificationId);
Secret secret = subscription.getSecret();
String endpoint = subscription.getPushEndpoint();
String secretType = secret.getSecretType();
String pushUrl = "";
Map<String, String> requestHeader = new HashMap<>();
// Authentication Secret
SecretAuth secretAuth= authFactory.getSecret(secretType);
secretAuth.setSecret(secret);
pushUrl = secretAuth.getPushUrl(endpoint);
secretAuth.getRequestHeaders(requestHeader);
// TODO: Extract out logic for multiple authentication codes
if (secretType.equalsIgnoreCase(HMAC_TYPE)) {
this.log.info("receiving pubsub message, will send out hmac type request, pubsub message: " + pubsubMessage);
HmacSecret hmacSecret = (HmacSecret) secret;
String signedjwt = this.signatureService.getSignedSignature(endpoint, hmacSecret.getValue());
pushUrl = endpoint + "?hmac=" + signedjwt;
} else if (secretType.equalsIgnoreCase(GSA_TYPE)) {
this.log.info("receiving pubsub message, will send out gsa type request, pubsub message: " + pubsubMessage);
GsaSecret gsaSecret = (GsaSecret) secret;
GsaSecretValue gsaSecretValue = gsaSecret.getValue();
JsonParser jsonParser = new JsonParser();
JsonElement root = jsonParser.parse(gsaSecretValue.getKey());
String keyString = root.getAsJsonObject().toString();
String idToken = this.gsaTokenProvider.getIdToken(keyString, gsaSecretValue.getAudience());
pushUrl = endpoint;
requestHeader.put("Authorization", idToken);
}
requestHeader.put(DpsHeaders.CONTENT_TYPE, "application/json");
requestHeader.put(DpsHeaders.CORRELATION_ID, headerAttributes.get(DpsHeaders.CORRELATION_ID));
requestHeader.put(DpsHeaders.DATA_PARTITION_ID, headerAttributes.get(DpsHeaders.DATA_PARTITION_ID));
HttpRequest request = HttpRequest.post().url(pushUrl).headers(requestHeader).body(pubsubMessage).connectionTimeout(WAITING_TIME).build();
HttpResponse response = httpClient.send(request);
if (!response.isSuccessCode()) {
this.log.error(NOT_ACKNOWLEDGE);
return ResponseEntity.badRequest().body(NOT_ACKNOWLEDGE);
}
this.log.info("sending out notification to endpoint: " + endpoint);
}catch(Exception e)
{
this.log.error("An exception occurred: " + e.getMessage());
}
this.log.info(ACKNOWLEDGE);
return ResponseEntity.ok(ACKNOWLEDGE);
return response;
}
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.service;
import com.fasterxml.jackson.databind.ObjectMapper;
......@@ -14,10 +30,11 @@ import org.opengroup.osdu.core.common.notification.SubscriptionException;
import org.opengroup.osdu.notification.di.SubscriptionCacheFactory;
import org.opengroup.osdu.notification.service.interfaces.ISubscriptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
@Component
public class SubscriptionHandler implements ISubscriptionHandler {
@Autowired
private ISubscriptionFactory subscriptionFactory;
......@@ -70,4 +87,8 @@ public class SubscriptionHandler implements ISubscriptionHandler {
}
return this.objectMapper;
}
//unit test purpose
void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
}
package org.opengroup.osdu.notification.service.interfaces;
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.springframework.http.ResponseEntity;
package org.opengroup.osdu.notification.service.interfaces;
import org.opengroup.osdu.core.common.http.HttpResponse;
import java.util.Map;
public interface INotificationHandler {
public ResponseEntity NotifySubscriber(String id, String notificationId, Map<String, String> headerAttributes);
HttpResponse NotifySubscriber(String id, String notificationId, Map<String, String> headerAttributes) throws Exception;
}
/*
* Copyright 2017-2020, Schlumberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.notification.service.interfaces;
import org.opengroup.osdu.core.common.model.notification.Subscription;
import org.opengroup.osdu.core.common.notification.SubscriptionException;
import java.io.IOException;
public interface ISubscriptionHandler {
public Subscription getSubscriptionFromCache(String notificationId) throws IOException, SubscriptionException;
Subscription getSubscriptionFromCache(String notificationId) throws IOException, SubscriptionException;
}
......@@ -16,39 +16,20 @@
package org.opengroup.osdu.notification.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.opengroup.osdu.core.common.cryptographic.ISignatureService;
import org.opengroup.osdu.core.common.http.HttpClient;
import org.opengroup.osdu.core.common.http.HttpResponse;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.http.DpsHeaders;
import org.opengroup.osdu.core.common.model.notification.Subscription;
import org.opengroup.osdu.core.common.notification.ISubscriptionService;
import org.opengroup.osdu.core.common.notification.SubscriptionException;
import org.opengroup.osdu.core.common.notification.SubscriptionFactory;
import org.opengroup.osdu.core.common.notification.SubscriptionService;
import org.opengroup.osdu.notification.di.CredentialHeadersProvider;
import org.opengroup.osdu.notification.di.SubscriptionCacheFactory;
import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount;
import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor;
import org.opengroup.osdu.notification.service.NotificationHandler;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.http.ResponseEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
......@@ -56,31 +37,14 @@ public class PubsubEndpointTests {
@Mock
private IPubsubRequestBodyExtractor pubsubRequestBodyExtractor;
@Mock
private ISignatureService signatureService;
@Mock
private HttpClient httpClient;
@Mock
private DpsHeaders headers;
@Mock
private SubscriptionCacheFactory subscriptionCacheFactory;
@Mock
private IGoogleServiceAccount googleIdTokenProducer;
private NotificationHandler notificationHandler;
@Mock
private JaxRsDpsLog log;
@InjectMocks
private PubsubEndpoint sut;
@Mock
private CredentialHeadersProvider credentialHeadersProvider;
@Mock
private SubscriptionFactory subscriptionFactory;
@Mock
private ObjectMapper objectMapper;
private static final String SIGNED_SIGNATURE = "testEncodedInfo.testSignature";
private static final String GOOGLE_ID_TOKEN = "testHeader.testPayload.testSignature";
private static final String NOTIFICATION_ID = "test-notification-id";
private static final String PUBSUB_MESSAGE = "test-pubsub-message-data";
private HttpResponse response = new HttpResponse();
@Before
......@@ -90,156 +54,29 @@ public class PubsubEndpointTests {
}
@Test
public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_hmac() throws Exception {
response.setResponseCode(200);
when(this.signatureService.getSignedSignature(any(), any())).thenReturn(SIGNED_SIGNATURE);
when(this.httpClient.send(any())).thenReturn(response);
when(this.subscriptionCacheFactory.get(any())).thenReturn(getHmacSubscription());
ResponseEntity responseEntity = this.sut.recordChanged();
Assert.assertEquals(200, responseEntity.getStatusCode().value());
Assert.assertEquals("message acknowledged by client", responseEntity.getBody().toString());
}
@Test
public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_gsa() throws Exception {
public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromNotificationHandler() throws Exception {
response.setResponseCode(200);
when(this.googleIdTokenProducer.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN);
when(this.httpClient.send(any())).thenReturn(response);
when(this.subscriptionCacheFactory.get(any())).thenReturn(getGsaSubscription());
when(this.notificationHandler.NotifySubscriber(anyString(), anyString(), any())).thenReturn(response);
ResponseEntity responseEntity = this.sut.recordChanged();
Assert.assertEquals(200, responseEntity.getStatusCode().value());
Assert.assertEquals("message acknowledged by client", responseEntity.getBody().toString());
}
@Test
public void should_return400_whenSendOutRequestButNoSuccessCodeReturned_hmac() throws Exception {
response.setResponseCode(500);
when(this.signatureService.getSignedSignature(any(), any())).thenReturn(SIGNED_SIGNATURE);
when(this.httpClient.send(any())).thenReturn(response);
when(this.subscriptionCacheFactory.get(any())).thenReturn(getHmacSubscription());