diff --git a/core/pom.xml b/core/pom.xml index 13401e2d..f65f31de 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -42,14 +42,17 @@ io.github.openfeign feign-core + io.github.openfeign feign-jackson + io.github.openfeign feign-slf4j + io.github.openfeign.form feign-form @@ -73,5 +76,4 @@ - diff --git a/events_webhook/pom.xml b/events_webhook/pom.xml new file mode 100644 index 00000000..6e74ab80 --- /dev/null +++ b/events_webhook/pom.xml @@ -0,0 +1,63 @@ + + + + + com.adobe.aio + aio-lib-java + 1.0.1-SNAPSHOT + ../pom.xml + + 4.0.0 + + Adobe I/O - Events Webhook Library + Adobe I/O - Java SDK - Events Webhook Library + + 11 + 11 + + aio-lib-java-events-webhook + + + + com.adobe.aio + aio-lib-java-core + ${project.version} + + + + com.adobe.aio + aio-lib-java-ims + ${project.version} + + + + com.google.code.findbugs + jsr305 + + + + + org.mockito + mockito-core + + + junit + junit + + + + \ No newline at end of file diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/api/PublicKeyCdnApi.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/api/PublicKeyCdnApi.java new file mode 100644 index 00000000..6e9bcba7 --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/api/PublicKeyCdnApi.java @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.api; + +import feign.Param; +import feign.RequestLine; + +public interface PublicKeyCdnApi { + @RequestLine(value = "GET {pubKeyPath}") + String getPubKeyFromCDN(@Param("pubKeyPath") String pubKeyPath); +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheService.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheService.java new file mode 100644 index 00000000..4750a12c --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheService.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.cache; + +import com.adobe.aio.event.webhook.model.CacheableObject; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface CacheService { + + @Nullable + Object get(@Nonnull String key); + + void put(@Nonnull String key, @Nonnull Object value); + + boolean isExpired(CacheableObject cacheable); + +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheServiceImpl.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheServiceImpl.java new file mode 100644 index 00000000..161eddf3 --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/cache/CacheServiceImpl.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.cache; + +import com.adobe.aio.event.webhook.model.CacheableObject; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class CacheServiceImpl implements CacheService { + + private static final Logger logger = LoggerFactory.getLogger(CacheServiceImpl.class); + private static final int DEFAULT_TTL_IN_MINUTES = 1440; + private static final Date CURRENT_SYSTEM_DATE = new Date(); + + Map cacheMap; + + @Nullable + @Override + public String get(@Nonnull String key) { + CacheableObject obj = (CacheableObject) cacheMap.get(key); + if (obj != null) { + if (isExpired(obj)) { + logger.debug("public key object in cache is expired..invalidating entry for key {}", key); + cacheMap.remove(key); + return null; + } else { + return obj.getValue(); + } + } + return null; + } + + @Override + public void put(@Nonnull String key, @Nonnull Object value) { + putWithExpiry(key, value, DEFAULT_TTL_IN_MINUTES); + } + + public void putWithExpiry(@Nonnull String key, @Nonnull Object value, int ttlInMinutes) { + CacheableObject cacheableObject = new CacheableObject(key, (String) value, + getExpirationDate(ttlInMinutes)); + cacheMap.put(key, cacheableObject); + } + + @Override + public boolean isExpired(CacheableObject cacheableObject) { + return CURRENT_SYSTEM_DATE.after(cacheableObject.getPubKeyExpiryDate()); + } + + private CacheServiceImpl initialiseCacheMap() { + this.cacheMap = new HashMap<>(); + return this; + } + + public static CacheBuilder cacheBuilder() { + return new CacheBuilder(); + } + + public static class CacheBuilder { + public CacheServiceImpl buildCache() { + return new CacheServiceImpl() + .initialiseCacheMap(); + } + } + + private Date getExpirationDate(int minutesToLive) { + Date expirationDate = new java.util.Date(); + java.util.Calendar cal = java.util.Calendar.getInstance(); + cal.setTime(expirationDate); + cal.add(cal.MINUTE, minutesToLive); + expirationDate = cal.getTime(); + return expirationDate; + } +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/feign/FeignPubKeyService.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/feign/FeignPubKeyService.java new file mode 100644 index 00000000..705db916 --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/feign/FeignPubKeyService.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.feign; + +import com.adobe.aio.event.webhook.api.PublicKeyCdnApi; +import com.adobe.aio.event.webhook.service.PubKeyService; +import com.adobe.aio.util.feign.FeignUtil; + +public class FeignPubKeyService implements PubKeyService { + + private final PublicKeyCdnApi publicKeyCdnApi; + + public FeignPubKeyService(final String pubKeyCdnBaseUrl) { + this.publicKeyCdnApi = FeignUtil.getBaseBuilder() + .target(PublicKeyCdnApi.class, pubKeyCdnBaseUrl); + } + + @Override + public String getPubKeyFromCDN(String pubKeyPath) { + String pubKey = publicKeyCdnApi.getPubKeyFromCDN(pubKeyPath); + return pubKey; + } +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/model/CacheableObject.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/model/CacheableObject.java new file mode 100644 index 00000000..839cfd9e --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/model/CacheableObject.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.model; + +import java.util.Date; +import java.util.Objects; + +public class CacheableObject { + + private String key; + private String value; + private Date pubKeyExpiryDate; + + public CacheableObject(String key, String value, Date expiryInMinutes) { + this.key = key; + this.value = value; + this.pubKeyExpiryDate = expiryInMinutes; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public Date getPubKeyExpiryDate() { + return pubKeyExpiryDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CacheableObject that = (CacheableObject) o; + return Objects.equals(key, that.key) && Objects.equals(value, that.value) + && Objects.equals(pubKeyExpiryDate, that.pubKeyExpiryDate); + } + + @Override + public int hashCode() { + return Objects.hash(key, value, pubKeyExpiryDate); + } + + @Override + public String toString() { + return "CacheableObject{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + ", pubKeyExpiryDate=" + pubKeyExpiryDate + + '}'; + } +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/EventVerifier.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/EventVerifier.java new file mode 100644 index 00000000..67fb2993 --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/EventVerifier.java @@ -0,0 +1,181 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.service; + +import static com.adobe.aio.event.webhook.cache.CacheServiceImpl.cacheBuilder; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.adobe.aio.event.webhook.cache.CacheServiceImpl; +import com.adobe.aio.event.webhook.feign.FeignPubKeyService; +import com.adobe.aio.exception.AIOException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EventVerifier { + + private static Logger logger = LoggerFactory.getLogger(EventVerifier.class); + + public static final String ADOBE_IOEVENTS_SECURITY_DOMAIN = "https://static.adobeioevents.com"; + public static final String ADOBE_IOEVENTS_DIGI_SIGN_1 = "x-adobe-digital-signature-1"; + public static final String ADOBE_IOEVENTS_DIGI_SIGN_2 = "x-adobe-digital-signature-2"; + public static final String ADOBE_IOEVENTS_PUB_KEY_1_PATH = "x-adobe-public-key1-path"; + public static final String ADOBE_IOEVENTS_PUB_KEY_2_PATH = "x-adobe-public-key2-path"; + + private final FeignPubKeyService pubKeyService; + + private CacheServiceImpl pubKeyCache; + + EventVerifier(String url) { + this.pubKeyService = new FeignPubKeyService(url); + + } + + public EventVerifier() { + this(ADOBE_IOEVENTS_SECURITY_DOMAIN); + this.pubKeyCache = cacheBuilder().buildCache(); + } + + /** + * Authenticate the event by checking the target recipient + * and verifying the signatures + * @param message - the event payload + * @param clientId - recipient client id in the payload + * @param headers - webhook request headers + * @return boolean - TRUE if valid event else FALSE + */ + public boolean authenticateEvent(String message, String clientId, Map headers) { + if (!isValidTargetRecipient(message, clientId)) { + logger.error("target recipient {} is not valid for message {}", clientId, message); + return false; + } + if (!verifyEventSignatures(message, headers)) { + logger.error("signatures are not valid for message {}", message); + return false; + } + return true; + } + + private boolean verifyEventSignatures(String message, Map headers) { + String[] digitalSignatures = {headers.get(ADOBE_IOEVENTS_DIGI_SIGN_1), + headers.get(ADOBE_IOEVENTS_DIGI_SIGN_2)}; + String[] pubKeyPaths = {headers.get(ADOBE_IOEVENTS_PUB_KEY_1_PATH), + headers.get(ADOBE_IOEVENTS_PUB_KEY_2_PATH)}; + return verifySignature(message, pubKeyPaths, digitalSignatures); + } + + private boolean verifySignature(String message, String[] publicKeyPaths, String[] signatures) { + byte[] data = message.getBytes(UTF_8); + + for (int i = 0; i < signatures.length; i++) { + try { + // signature generated at I/O Events side is Base64 encoded, so it must be decoded + byte[] sign = Base64.getDecoder().decode(signatures[i]); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(getPublic(fetchPemEncodedPublicKey(publicKeyPaths[i]))); + sig.update(data); + boolean isSignValid = sig.verify(sign); + if (isSignValid) { + return true; + } + } catch (GeneralSecurityException e) { + throw new AIOException("Error verifying signature for public key " + publicKeyPaths[i] + +". Reason -> " + e.getMessage(), e); + } + } + return false; + } + + private boolean isValidTargetRecipient(String message, String clientId) { + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonPayload = mapper.readTree(message); + JsonNode recipientClientIdNode = jsonPayload.get("recipient_client_id"); + return (recipientClientIdNode != null && recipientClientIdNode.textValue() !=null + && recipientClientIdNode.textValue().equals(clientId)); + } catch (JsonProcessingException e) { + throw new AIOException("error parsing the event payload during target recipient check.."); + } + } + + private PublicKey getPublic(String pubKey) + throws NoSuchAlgorithmException, InvalidKeySpecException { + String publicKeyPEM = pubKey + .replace("-----BEGIN PUBLIC KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END PUBLIC KEY-----", ""); + + byte[] keyBytes = Base64.getDecoder().decode(publicKeyPEM); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + return keyFactory.generatePublic(keySpec); + } + + String fetchPemEncodedPublicKey(String publicKeyPath) { + return fetchKeyFromCacheOrApi(publicKeyPath); + } + + private String fetchKeyFromCacheOrApi(String pubKeyPath) { + String pubKeyFileName = getPublicKeyFileName(pubKeyPath); + String pubKey = getKeyFromCache(pubKeyFileName); + if (StringUtils.isEmpty(pubKey)) { + pubKey = fetchKeyFromApiAndPutInCache(pubKeyPath, pubKeyFileName); + } + return pubKey; + } + + private String fetchKeyFromApiAndPutInCache(String pubKeyPath, String pubKeyFileName) { + try { + logger.warn("public key {} not present in cache, fetching directly from the cdn url {}", + pubKeyFileName, ADOBE_IOEVENTS_SECURITY_DOMAIN + pubKeyPath); + String pubKeyFetchResponse = pubKeyService.getPubKeyFromCDN(pubKeyPath); + if (!StringUtils.isEmpty(pubKeyFetchResponse)) { + pubKeyCache.put(pubKeyFileName, pubKeyFetchResponse); + } + return pubKeyFetchResponse; + } catch (Exception e) { + throw new AIOException("error fetching public key from CDN url -> " + + ADOBE_IOEVENTS_SECURITY_DOMAIN + pubKeyPath + " due to " + e.getMessage()); + } + } + + private String getKeyFromCache(String pubKeyFileNameAsKey) { + Object pubKey = pubKeyCache.get(pubKeyFileNameAsKey); + if (pubKey != null) { + logger.debug("fetched key successfully for pub key path {} from cache", pubKeyFileNameAsKey); + return String.valueOf(pubKey); + } + return null; + } + + /** + * Parses the pub key file name from the relative path + * + * @param pubKeyPath - relative path in the format /prod/keys/pub-key-voy5XEbWmT.pem + * @return public key file name + */ + private String getPublicKeyFileName(String pubKeyPath) { + return pubKeyPath.substring(pubKeyPath.lastIndexOf('/') + 1); + } +} diff --git a/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/PubKeyService.java b/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/PubKeyService.java new file mode 100644 index 00000000..74026ef0 --- /dev/null +++ b/events_webhook/src/main/java/com/adobe/aio/event/webhook/service/PubKeyService.java @@ -0,0 +1,16 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +package com.adobe.aio.event.webhook.service; + +public interface PubKeyService { + String getPubKeyFromCDN(String pubKeyPath); +} diff --git a/events_webhook/src/test/java/com/adobe/aio/event/webhook/service/EventVerifierTest.java b/events_webhook/src/test/java/com/adobe/aio/event/webhook/service/EventVerifierTest.java new file mode 100644 index 00000000..615d2548 --- /dev/null +++ b/events_webhook/src/test/java/com/adobe/aio/event/webhook/service/EventVerifierTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Adobe. All rights reserved. + * This file is licensed to you 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 REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +package com.adobe.aio.event.webhook.service; + +import static com.adobe.aio.event.webhook.service.EventVerifier.ADOBE_IOEVENTS_DIGI_SIGN_1; +import static com.adobe.aio.event.webhook.service.EventVerifier.ADOBE_IOEVENTS_DIGI_SIGN_2; +import static com.adobe.aio.event.webhook.service.EventVerifier.ADOBE_IOEVENTS_PUB_KEY_1_PATH; +import static com.adobe.aio.event.webhook.service.EventVerifier.ADOBE_IOEVENTS_PUB_KEY_2_PATH; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class EventVerifierTest { + + private static final String TEST_CLIENT_ID = "client_id1"; + private static final String INVALID_TEST_CLIENT_ID = "invalid_client_id"; + private static final String TEST_DIGI_SIGN_1 = "pZY22OGm8/6H6bJXSi+/4VztsPN+fPZtHgHrrASuTw7LTUZVpbAZNaXVTzQsFd47PvaI8aQxbl874GFmH0QfAVQaRT93x5O/kQdM1ymG03303QaFY/mjm/Iot3VEwq5xOtM8f5a2mKUce9bgEv28iN7z9H/MbBOSmukPSJh/vMLkFAmMZQwdP4SRK3ckxQg6wWTbeMRxjw8/FLckznCGPZri4c0O7WPr8wnrWcvArlhBpIPJPeifJOyDj/woFQzoeemdrVoBFOieE/j3RoMWzcQeLENaSrqk00MPL2svNQcTLMkmWuICOjYSbnlv/EPFCQS8bQsnVHxGFD1yDeFa7Q=="; + private static final String TEST_DIGI_SIGN_2 = "GpMONiPMHY51vpHF3R9SSs9ogRn8i2or/bvV3R+PYXGgDzAxDhRdl9dIUp/qQ3vsxDGEv045IV4GQ2f4QbsFvWLJsBNyCqLs6KL8LsRoGfEC4Top6c1VVjrEEQ1MOoFcoq/6riXzg4h09lRTfARllVv+icgzAiuv/JW2HNg5yQ4bqenFELD6ipCStuaI/OGS0A9s0Hc6o3aoHz3r5d5DecwE6pUdpG8ODhKBM+34CvcvMDNdrj8STYWHsEUqGdR9klpaqaC1QRYFIO7WgbgdwsuGULz6Sjm+q5s5Wh++fz5E+gXkizFviD389gDIUylFTig/1h7WTLRDuSz69Q+C5w=="; + private static final String TEST_INVALID_DIGI_SIGN_1 = "abc22OGm8/6H6bJXSi+/4VztsPN+fPZtHgHrrASuTw7LTUZVpbAZNaXVTzQsFd47PvaI8aQxbl874GFmH0QfAVQaRT93x5O/kQdM1ymG03303QaFY/mjm/Iot3VEwq5xOtM8f5a2mKUce9bgEv28iN7z9H/MbBOSmukPSJh/vMLkFAmMZQwdP4SRK3ckxQg6wWTbeMRxjw8/FLckznCGPZri4c0O7WPr8wnrWcvArlhBpIPJPeifJOyDj/woFQzoeemdrVoBFOieE/j3RoMWzcQeLENaSrqk00MPL2svNQcTLMkmWuICOjYSbnlv/EPFCQS8bQsnVHxGFD1yDeFa7Q=="; + private static final String TEST_PUB_KEY1_PATH = "/stage/keys/pub-key-3fVj0Lv0QB.pem"; + private static final String TEST_PUB_KEY2_PATH = "/stage/keys/pub-key-sKNHrkauqi.pem"; + + private EventVerifier underTest; + + @Before + public void setup() { + underTest = new EventVerifier(); + } + + @Test + public void testVerifyValidSignature() { + String message = getTestMessage(); + Map headers = getTestHeadersWithValidSignature(); + boolean result = underTest.authenticateEvent(message, TEST_CLIENT_ID, headers); + Assert.assertEquals(true, result); + } + + @Test + public void testVerifyInvalidSignature() { + String message = getTestMessage(); + Map headers = getTestHeadersWithInvalidSignature(); + boolean result = underTest.authenticateEvent(message, TEST_CLIENT_ID, headers); + Assert.assertEquals(Boolean.FALSE, result); + } + + @Test + public void testVerifyInvalidPublicKey() { + String message = getTestMessage(); + Map headers = getTestHeadersWithInvalidPubKey(); + boolean result = underTest.authenticateEvent(message, TEST_CLIENT_ID, headers); + Assert.assertEquals(Boolean.FALSE, result); + } + + @Test + public void testVerifyInvalidRecipientClient() { + String message = getTestMessage(); + Map headers = getTestHeadersWithInvalidPubKey(); + boolean result = underTest.authenticateEvent(message, INVALID_TEST_CLIENT_ID, headers); + Assert.assertEquals(Boolean.FALSE, result); + } + + // ============================ PRIVATE HELPER METHODS ================================ + private String getTestMessage() { + return "{\"event_id\":\"eventId1\",\"event\":{\"hello\":\"world\"},\"recipient_client_id\":\"client_id1\"}"; + } + + private Map getTestSignatureHeaders() { + Map testSignatureHeaders = new HashMap<>(); + testSignatureHeaders.put(ADOBE_IOEVENTS_DIGI_SIGN_1, TEST_DIGI_SIGN_1); + testSignatureHeaders.put(ADOBE_IOEVENTS_DIGI_SIGN_2, TEST_DIGI_SIGN_2); + testSignatureHeaders.put(ADOBE_IOEVENTS_PUB_KEY_1_PATH, TEST_PUB_KEY1_PATH); + testSignatureHeaders.put(ADOBE_IOEVENTS_PUB_KEY_2_PATH, TEST_PUB_KEY2_PATH); + return testSignatureHeaders; + } + + private Map getTestHeadersWithValidSignature() { + return getTestSignatureHeaders(); + } + + private Map getTestHeadersWithInvalidSignature() { + Map signHeaders = getTestSignatureHeaders(); + signHeaders.put(ADOBE_IOEVENTS_DIGI_SIGN_1, TEST_INVALID_DIGI_SIGN_1); + signHeaders.put(ADOBE_IOEVENTS_DIGI_SIGN_2, TEST_INVALID_DIGI_SIGN_1); + return signHeaders; + } + + private Map getTestHeadersWithInvalidPubKey() { + Map signHeaders = getTestSignatureHeaders(); + signHeaders.put(ADOBE_IOEVENTS_PUB_KEY_1_PATH, TEST_PUB_KEY2_PATH); + signHeaders.put(ADOBE_IOEVENTS_PUB_KEY_2_PATH, TEST_PUB_KEY1_PATH); + return signHeaders; + } +} diff --git a/pom.xml b/pom.xml index 01bda0f2..d77586f7 100644 --- a/pom.xml +++ b/pom.xml @@ -28,10 +28,9 @@ events_mgmt events_ingress events_journal - + events_webhook events_test events_xdm - aem @@ -108,6 +107,7 @@ 2.12.3 0.11.2 1.2.0 + 5.3.22 1.0.9 @@ -212,6 +212,16 @@ feign-form ${feign-form.version} + + org.springframework + spring-context + ${spring-context.version} + + + com.google.code.findbugs + jsr305 + 3.0.2 + junit