diff --git a/.github/workflows/samples-compilation b/.github/workflows/samples-compilation.yaml similarity index 68% rename from .github/workflows/samples-compilation rename to .github/workflows/samples-compilation.yaml index b9ea577f..50322bac 100644 --- a/.github/workflows/samples-compilation +++ b/.github/workflows/samples-compilation.yaml @@ -1,4 +1,4 @@ -name: Java CI with Maven +name: Samples Compilation on: [push] @@ -15,10 +15,12 @@ jobs: java-version: '21' distribution: 'temurin' cache: maven - - name: Build Services samples with Maven - run: cd sample-app; mvn -B clean package - - name: Build Webhooks samples with Maven - run: cd sample-app; mvn -B -f pom-webhooks.xml clean package + - name: Building + run: | + mvn clean verify install -DskipTests=true -Dspotless.apply.skip=true + cd sample-app + mvn -B clean package + mvn -B -f pom-webhooks.xml clean package # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/README.md b/README.md index 5e4a04f0..b69539a0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Java SDK [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/sinch/sinch-sdk-python/blob/main/LICENSE) -[![Python 3.8](https://img.shields.io/badge/Java-8-blue.svg)](https://docs.oracle.com/javase/8) +[![Java 8](https://img.shields.io/badge/Java-8-blue.svg)](https://docs.oracle.com/javase/8) @@ -34,7 +34,6 @@ For more in depth version of the Sinch APIs, please refer to the official develo - JDK 8 or later - [Maven](https://maven.apache.org/) -- [Maven Central](https://mvnrepository.com/artifact/com.sinch.sdk/sinch-java-sdk) - [Sinch account](https://dashboard.sinch.com) ## Installation diff --git a/client/src/main/com/sinch/sdk/SinchClient.java b/client/src/main/com/sinch/sdk/SinchClient.java index 68434532..007ac10e 100644 --- a/client/src/main/com/sinch/sdk/SinchClient.java +++ b/client/src/main/com/sinch/sdk/SinchClient.java @@ -21,7 +21,7 @@ public class SinchClient { private static final String DEFAULT_PROPERTIES_FILE_NAME = "/config-default.properties"; - private static final String VERSION_PROPERTIES_FILE_NAME = "/version.properties"; + private static final String VERSION_PROPERTIES_FILE_NAME = "/version.properties"; private static final String OAUTH_URL_KEY = "oauth-url"; private static final String NUMBERS_SERVER_KEY = "numbers-server"; @@ -224,4 +224,5 @@ private String formatAuxiliaryFlag(String auxiliaryFlag) { } return String.join(",", values); } + } diff --git a/client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java b/client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java index e9a13a28..000f4f56 100644 --- a/client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java +++ b/client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java @@ -34,7 +34,7 @@ public void resetToken() { @Override public Collection> getAuthorizationHeaders( - String method, String httpContentType, String path, String body) { + String timestamp, String method, String httpContentType, String path, String body) { String key = keyId == null ? "" : keyId; String secret = keySecret == null ? "" : keySecret; diff --git a/client/src/main/com/sinch/sdk/auth/adapters/BearerAuthManager.java b/client/src/main/com/sinch/sdk/auth/adapters/BearerAuthManager.java index b0a332a8..664466ee 100644 --- a/client/src/main/com/sinch/sdk/auth/adapters/BearerAuthManager.java +++ b/client/src/main/com/sinch/sdk/auth/adapters/BearerAuthManager.java @@ -64,7 +64,7 @@ public void resetToken() { @Override public Collection> getAuthorizationHeaders( - String method, String httpContentType, String path, String body) { + String timestamp, String method, String httpContentType, String path, String body) { if (token == null) { refreshToken(); diff --git a/client/src/main/com/sinch/sdk/auth/adapters/VerificationApplicationAuthManager.java b/client/src/main/com/sinch/sdk/auth/adapters/VerificationApplicationAuthManager.java index 86ec299b..4fa13cdd 100644 --- a/client/src/main/com/sinch/sdk/auth/adapters/VerificationApplicationAuthManager.java +++ b/client/src/main/com/sinch/sdk/auth/adapters/VerificationApplicationAuthManager.java @@ -11,7 +11,6 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.time.Instant; import java.util.Arrays; import java.util.Base64; import java.util.Collection; @@ -42,7 +41,7 @@ public void resetToken() { @Override public Collection> getAuthorizationHeaders( - String method, String httpContentType, String path, String body) { + String timestamp, String method, String httpContentType, String path, String body) { String decodePath; try { @@ -52,7 +51,6 @@ public Collection> getAuthorizationHeaders( } // see // https://developers.sinch.com/docs/verification/api-reference/authentication/signed-request/ - Instant timestamp = Instant.now(); String bodyMD5Hash = getBodyMD5Hash(body); String stringToSign = getSignature(method, bodyMD5Hash, httpContentType, timestamp, decodePath); String encoded = encode(stringToSign); @@ -60,7 +58,7 @@ public Collection> getAuthorizationHeaders( return Arrays.asList( new Pair<>("Authorization", AUTH_KEYWORD + " " + key + ":" + encoded), - new Pair<>(XTIMESTAMP_HEADER, timestamp.toString())); + new Pair<>(XTIMESTAMP_HEADER, timestamp)); } private String getBodyMD5Hash(String body) { @@ -78,13 +76,13 @@ private String getBodyMD5Hash(String body) { } private String getSignature( - String method, String bodyMD5Hash, String httpContentType, Instant timestamp, String path) { + String method, String bodyMD5Hash, String httpContentType, String timestamp, String path) { return String.join( "\n", method, bodyMD5Hash, null != httpContentType ? httpContentType : "", - XTIMESTAMP_HEADER + ":" + timestamp.toString(), + XTIMESTAMP_HEADER + ":" + timestamp, path); } diff --git a/client/src/main/com/sinch/sdk/domains/verification/VerificationService.java b/client/src/main/com/sinch/sdk/domains/verification/VerificationService.java index d54585ce..0032a844 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/VerificationService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/VerificationService.java @@ -10,29 +10,26 @@ public interface VerificationService { /** - * Use application secret in place of unified configuration for authentication (see Sinch - * dashboard for details) These credentials are related to Verification Apps + * Verifications Service instance * - * @param key see dashboard - * @param secret see dashboard * @return service instance for project * @since 1.0 */ - VerificationService setApplicationCredentials(String key, String secret); + VerificationsService verifications(); /** - * Verifications Service instance + * Status Service instance * * @return service instance for project * @since 1.0 */ - VerificationsService verifications(); + StatusService status(); /** - * Status Service instance + * Webhooks helpers instance * - * @return service instance for project + * @return instance service related to webhooks helpers * @since 1.0 */ - StatusService status(); + WebHooksService webhooks(); } diff --git a/client/src/main/com/sinch/sdk/domains/verification/WebHooksService.java b/client/src/main/com/sinch/sdk/domains/verification/WebHooksService.java new file mode 100644 index 00000000..757b8b3b --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/WebHooksService.java @@ -0,0 +1,62 @@ +package com.sinch.sdk.domains.verification; + +import com.sinch.sdk.core.exceptions.ApiMappingException; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationEvent; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponse; +import java.util.Map; + +/** + * Webhooks service + *

+ * Callback events are used to authorize and manage your verification requests and return + * verification results. + *

+ * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/#tag/Verification-callbacks/paths/VerificationRequestEvent/post + * + * @since 1.0 + */ +public interface WebHooksService { + + /** + * The Sinch Platform can initiate callback requests to a URL you define (Callback URL) on request + * and result events. All callback requests are signed using your Application key and secret pair + * found on your dashboard. The signature is included in the Authorization header of the request + *

By using following function, you can ensure authentication according to received payload + * from your backend

+ * + * @param method The HTTP method used ot handle the callback + * @param path The path to you backend endpoint used for callback + * @param headers Received headers + * @param jsonPayload Received payload + * @return Is authentication is validated (true) or not (false) + * + * see https://developers.sinch.com/docs/verification/api-reference/authentication/callback-signed-request + * @since 1.0 + */ + boolean checkAuthentication( + String method, String path, Map headers, String jsonPayload); + + /** + * This function can be called to deserialize received payload onto callback onto proper java + * verification event class + * + * @param jsonPayload Received payload to be deserialized + * @return The verification event instance class + * + * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/ + * @since 1.0 + */ + VerificationEvent unserializeVerificationEvent(String jsonPayload) throws ApiMappingException; + + /** + * This function can be called to serialize a verification response to be send as JSON + * + * @param response The response to be serialized + * @return The JSON string to be sent + * + * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/ + * @since 1.0 + */ + String serializeVerificationResponse(VerificationResponse response) throws ApiMappingException; +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/StatusService.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/StatusService.java index 675b86aa..2cb7ce56 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/adapters/StatusService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/StatusService.java @@ -14,31 +14,26 @@ import com.sinch.sdk.domains.verification.models.VerificationReport; import com.sinch.sdk.models.Configuration; import java.util.Map; -import java.util.function.Supplier; public class StatusService implements com.sinch.sdk.domains.verification.StatusService { - private final Configuration configuration; - private final HttpClient httpClient; - private final Supplier> authManagerSupplier; + private final QueryVerificationsApi api; public StatusService( Configuration configuration, HttpClient httpClient, - Supplier> authManagerSupplier) { - this.configuration = configuration; - this.httpClient = httpClient; - this.authManagerSupplier = authManagerSupplier; - } - - protected QueryVerificationsApi getApi() { - return new QueryVerificationsApi( + Map authManagers) { + this.api = new QueryVerificationsApi( httpClient, configuration.getVerificationServer(), - authManagerSupplier.get(), + authManagers, new HttpMapper()); } + protected QueryVerificationsApi getApi() { + return this.api; + } + public VerificationReport get(Identity identity, VerificationMethodType method) { if (!(identity instanceof NumberIdentity)) { throw new ApiException("Unexpected entity: " + identity); diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java index 07e5285a..0c7e10b8 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationService.java @@ -7,44 +7,62 @@ import com.sinch.sdk.core.http.HttpClient; import com.sinch.sdk.domains.verification.StatusService; import com.sinch.sdk.domains.verification.VerificationsService; +import com.sinch.sdk.domains.verification.WebHooksService; import com.sinch.sdk.models.Configuration; -import java.util.AbstractMap; import java.util.Map; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Objects; +import java.util.TreeMap; public class VerificationService implements com.sinch.sdk.domains.verification.VerificationService { // FIXME: Verification OAS file claim it support "Basic" but miss the "Application" definition // trick to adapt the mapping of "Basic" keyword to the dedicated "Application" auth manager - private static final String SECURITY_SCHEME_KEYWORD_VERIFICATION = "Basic"; + private static final String BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION = "Basic"; + + private static final String APPLICATION_SECURITY_SCHEME_KEYWORD_VERIFICATION = "Application"; private final Configuration configuration; private final HttpClient httpClient; private VerificationsService verifications; private StatusService status; - private Map authManagers; - private final Supplier> authManagersSupplier = () -> authManagers; + private WebHooksService webhooks; + private Map clientAuthManagers; + private Map webhooksAuthManagers; public VerificationService(Configuration configuration, HttpClient httpClient) { + + // Currently, we are not supporting unified credentials: ensure application credentials are defined + Objects.requireNonNull(configuration.getApplicationKey(), "'applicationKey' cannot be null"); + Objects.requireNonNull(configuration.getApplicationSecret(), + "'applicationSecret' cannot be null"); + this.configuration = configuration; this.httpClient = httpClient; + setApplicationCredentials(configuration.getApplicationKey(),configuration.getApplicationSecret() ); } - public VerificationService setApplicationCredentials(String key, String secret) { + private void setApplicationCredentials(String key, String secret) { + + AuthManager basicAuthManager = new BasicAuthManager(key, secret); + AuthManager applicationAuthManager = new VerificationApplicationAuthManager(key, secret); - AuthManager authManager; boolean useApplicationAuth = true; - if (useApplicationAuth) { - authManager = new VerificationApplicationAuthManager(key, secret); - } else { - authManager = new BasicAuthManager(key, secret); - } - authManagers = - Stream.of(new AbstractMap.SimpleEntry<>(SECURITY_SCHEME_KEYWORD_VERIFICATION, authManager)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - return this; + // to handle request from client we can only have "Basic" keyword behind the auth managers + // because of the OAS file only contains it; so we need to trick the application auth manager + // hidden behind the "Basic" keyword + // we need both auth manager to handle webhooks because of customer will choose from his + // dashboard which scheme to be used + clientAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + clientAuthManagers.put( + BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION, + useApplicationAuth ? applicationAuthManager : basicAuthManager); + + // here we need both auth managers to handle webhooks because we are receiving an Authorization + // header with "Application" keyword + webhooksAuthManagers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + webhooksAuthManagers.put(BASIC_SECURITY_SCHEME_KEYWORD_VERIFICATION, basicAuthManager); + webhooksAuthManagers.put( + APPLICATION_SECURITY_SCHEME_KEYWORD_VERIFICATION, applicationAuthManager); } public VerificationsService verifications() { @@ -52,7 +70,7 @@ public VerificationsService verifications() { checkCredentials(); this.verifications = new com.sinch.sdk.domains.verification.adapters.VerificationsService( - configuration, httpClient, authManagersSupplier); + configuration, httpClient, clientAuthManagers); } return this.verifications; } @@ -62,13 +80,23 @@ public StatusService status() { checkCredentials(); this.status = new com.sinch.sdk.domains.verification.adapters.StatusService( - configuration, httpClient, authManagersSupplier); + configuration, httpClient, clientAuthManagers); } return this.status; } + public WebHooksService webhooks() { + checkCredentials(); + if (null == this.webhooks) { + this.webhooks = + new com.sinch.sdk.domains.verification.adapters.WebHooksService( + webhooksAuthManagers); + } + return this.webhooks; + } + private void checkCredentials() throws ApiAuthException { - if (null == authManagers || authManagers.isEmpty()) { + if (null == clientAuthManagers || clientAuthManagers.isEmpty()) { throw new ApiAuthException( String.format( "Service '%s' cannot be called without defined credentials", diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationsService.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationsService.java index 3fe7a5f4..ee6dd262 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationsService.java +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/VerificationsService.java @@ -15,32 +15,27 @@ import com.sinch.sdk.domains.verification.models.response.StartVerificationResponse; import com.sinch.sdk.models.Configuration; import java.util.Map; -import java.util.function.Supplier; public class VerificationsService implements com.sinch.sdk.domains.verification.VerificationsService { - private final Configuration configuration; - private final HttpClient httpClient; - private final Supplier> authManagerSupplier; + private final SendingAndReportingVerificationsApi api; public VerificationsService( Configuration configuration, HttpClient httpClient, - Supplier> authManagerSupplier) { - this.configuration = configuration; - this.httpClient = httpClient; - this.authManagerSupplier = authManagerSupplier; - } - - protected SendingAndReportingVerificationsApi getApi() { - return new SendingAndReportingVerificationsApi( + Map authManagers) { + this.api = new SendingAndReportingVerificationsApi( httpClient, configuration.getVerificationServer(), - authManagerSupplier.get(), + authManagers, new HttpMapper()); } + protected SendingAndReportingVerificationsApi getApi() { + return this.api; + } + public StartVerificationResponse start(StartVerificationRequestParameters parameters) { return VerificationsDtoConverter.convert( getApi().startVerification(VerificationsDtoConverter.convert(parameters))); diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/WebHooksService.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/WebHooksService.java new file mode 100644 index 00000000..915b75cc --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/WebHooksService.java @@ -0,0 +1,91 @@ +package com.sinch.sdk.domains.verification.adapters; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.core.exceptions.ApiMappingException; +import com.sinch.sdk.core.http.AuthManager; +import com.sinch.sdk.core.utils.Pair; +import com.sinch.sdk.core.utils.databind.Mapper; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationEvent; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponse; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +public class WebHooksService implements com.sinch.sdk.domains.verification.WebHooksService { + + private final Map authManagers; + + public WebHooksService(Map authManagers) { + this.authManagers = authManagers; + } + + public boolean checkAuthentication( + String method, String path, Map headers, String jsonPayload) { + + // convert header keys to use case-insensitive map keys + Map ciHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + ciHeaders.putAll(headers); + + String authorizationHeader = ciHeaders.get("Authorization"); + + // no authorization required + if (null == authorizationHeader) { + return true; + } + + String[] split = authorizationHeader.split(" "); + String authorizationKeyword = split.length > 0 ? split[0] : ""; + String authorizationHash = split.length > 1 ? split[1] : ""; + + String computedHash = computeHash(ciHeaders, authorizationKeyword, method, path, jsonPayload); + + return computedHash.equals(authorizationHash); + } + + private String computeHash(Map ciHeaders, String authorizationKeyword, + String method, String path, String jsonPayload) { + // getting content type header + String contentTypeHeader = ciHeaders.getOrDefault("content-type", ""); + + // getting x-timestamp header + String xTimeStampHeader = ciHeaders.get("x-timestamp"); + + // getting manager related to Authorization header value + AuthManager authManager = authManagers.get(authorizationKeyword); + + // compute locally according to inputs + Collection> computedHeaders = + authManager.getAuthorizationHeaders( + xTimeStampHeader, method, contentTypeHeader, path, jsonPayload); + + // get locally computed hash by auth manager + String computedAuthorization = + computedHeaders.stream() + .filter(f -> f.getLeft().equals("Authorization")) + .findFirst() + .map(Pair::getRight) + .orElse(""); + String[] newSplit = computedAuthorization.split(" "); + return newSplit.length > 1 ? newSplit[1] : ""; + } + + @Override + public VerificationEvent unserializeVerificationEvent(String jsonPayload) + throws ApiMappingException { + try { + return Mapper.getInstance().readValue(jsonPayload, VerificationEvent.class); + } catch (JsonProcessingException e) { + throw new ApiMappingException(jsonPayload, e); + } + } + + @Override + public String serializeVerificationResponse(VerificationResponse response) + throws ApiMappingException { + try { + return Mapper.getInstance().writeValueAsString(response); + } catch (JsonProcessingException e) { + throw new ApiMappingException(response.toString(), e); + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverter.java b/client/src/main/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverter.java index 628bdee9..e15944e1 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverter.java +++ b/client/src/main/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverter.java @@ -12,6 +12,7 @@ import com.sinch.sdk.domains.verification.models.VerificationReportReasonType; import com.sinch.sdk.domains.verification.models.VerificationReportSMS; import com.sinch.sdk.domains.verification.models.VerificationReportStatusType; +import com.sinch.sdk.domains.verification.models.VerificationSourceType; import com.sinch.sdk.domains.verification.models.dto.v1.CalloutVerificationReportRequestDto; import com.sinch.sdk.domains.verification.models.dto.v1.FlashCallInitiateVerificationResponseDto; import com.sinch.sdk.domains.verification.models.dto.v1.FlashcallOptionsDto; @@ -205,7 +206,11 @@ public static VerificationReport convert(VerificationResponseDto dto) { case FLASHCALL: { VerificationReportFlashCall.Builder abuilder = - VerificationReportFlashCall.builder().setSource(dto.getSource()); + VerificationReportFlashCall.builder(); + + if (null != dto.getSource()) { + abuilder.setSource(VerificationSourceType.from(dto.getSource())); + } if (null != dto.getPrice() && null != dto.getPrice().getVerificationPriceInformationDto()) { VerificationPriceInformationDto price = @@ -222,7 +227,10 @@ public static VerificationReport convert(VerificationResponseDto dto) { case SMS: { VerificationReportSMS.Builder abuilder = - VerificationReportSMS.builder().setSource(dto.getSource()); + VerificationReportSMS.builder(); + if (null != dto.getSource()) { + abuilder.setSource(VerificationSourceType.from(dto.getSource())); + } if (null != dto.getPrice() && null != dto.getPrice().getVerificationPriceInformationDto()) { VerificationPriceInformationDto price = diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportFlashCall.java b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportFlashCall.java index d5f9645a..4f771020 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportFlashCall.java +++ b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportFlashCall.java @@ -5,7 +5,7 @@ public class VerificationReportFlashCall extends VerificationReport { private final Price verificationPrice; private final Price terminationPrice; private final Integer billableDuration; - private final String source; + private final VerificationSourceType source; /** * @param id The unique ID of the verification request * @param status The status of the verification request @@ -23,8 +23,7 @@ public class VerificationReportFlashCall extends VerificationReport { * termination debiting is enabled (disabled by default). Depending on the type of rounding * used, the value is the actual call time rounded to the nearest second, minute or other * value. - * @param source Free text that the client is sending, used to show if the call/SMS was - * intercepted or not + * @param source Used to show if the call was intercepted or not */ public VerificationReportFlashCall( VerificationId id, @@ -34,7 +33,7 @@ public VerificationReportFlashCall( Price verificationPrice, Price terminationPrice, Integer billableDuration, - String source) { + VerificationSourceType source) { super(id, status, reason, reference); this.verificationPrice = verificationPrice; this.terminationPrice = terminationPrice; @@ -54,7 +53,7 @@ public Integer getBillableDuration() { return billableDuration; } - public String getSource() { + public VerificationSourceType getSource() { return source; } @@ -83,7 +82,7 @@ public static class Builder extends VerificationReport.Builder { Price verificationPrice; Price terminationPrice; Integer billableDuration; - String source; + VerificationSourceType source; public Builder setVerificationPrice(Price verificationPrice) { this.verificationPrice = verificationPrice; @@ -100,7 +99,7 @@ public Builder setBillableDuration(Integer billableDuration) { return this; } - public Builder setSource(String source) { + public Builder setSource(VerificationSourceType source) { this.source = source; return this; } diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportSMS.java b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportSMS.java index ae40a6b5..797d7c4d 100644 --- a/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportSMS.java +++ b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationReportSMS.java @@ -3,7 +3,7 @@ public class VerificationReportSMS extends VerificationReport { private final Price verificationPrice; - private final String source; + private final VerificationSourceType source; /** * @param id The unique ID of the verification request * @param status The status of the verification request @@ -13,8 +13,7 @@ public class VerificationReportSMS extends VerificationReport { * @param verificationPrice The maximum price charged for this verification process. This property * will appear in the body of the response with a delay. It will become visible only when the * verification status is other than PENDING - * @param source Free text that the client is sending, used to show if the call/SMS was - * intercepted or not + * @param source Used to show if the SMS was intercepted or not */ public VerificationReportSMS( VerificationId id, @@ -22,7 +21,7 @@ public VerificationReportSMS( VerificationReportReasonType reason, VerificationReference reference, Price verificationPrice, - String source) { + VerificationSourceType source) { super(id, status, reason, reference); this.verificationPrice = verificationPrice; this.source = source; @@ -32,7 +31,7 @@ public Price getVerificationPrice() { return verificationPrice; } - public String getSource() { + public VerificationSourceType getSource() { return source; } @@ -55,14 +54,14 @@ public static Builder builder() { public static class Builder extends VerificationReport.Builder { Price verificationPrice; - String source; + VerificationSourceType source; public Builder setVerificationPrice(Price verificationPrice) { this.verificationPrice = verificationPrice; return this; } - public Builder setSource(String source) { + public Builder setSource(VerificationSourceType source) { this.source = source; return this; } diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/VerificationSourceType.java b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationSourceType.java new file mode 100644 index 00000000..f7061697 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/VerificationSourceType.java @@ -0,0 +1,42 @@ +package com.sinch.sdk.domains.verification.models; + +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * The type of the verification request authorized values + * + * @since 1.0 + */ +public class VerificationSourceType extends EnumDynamic { + + public static final VerificationSourceType INTERCEPTED = new VerificationSourceType("intercepted"); + + public static final VerificationSourceType MANUAL = new VerificationSourceType("manual"); + + + /** */ + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + VerificationSourceType.class, + VerificationSourceType::new, + Arrays.asList(INTERCEPTED, MANUAL)); + + private VerificationSourceType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static VerificationSourceType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(VerificationSourceType e) { + return ENUM_SUPPORT.valueOf(e); + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/package-info.java b/client/src/main/com/sinch/sdk/domains/verification/models/package-info.java new file mode 100644 index 00000000..cbe8767e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/package-info.java @@ -0,0 +1,6 @@ +/** + * Verification API related models + * + * @since 1.0 + */ +package com.sinch.sdk.domains.verification.models; diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/requests/package-info.java b/client/src/main/com/sinch/sdk/domains/verification/models/requests/package-info.java new file mode 100644 index 00000000..0e7e9526 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/requests/package-info.java @@ -0,0 +1,6 @@ +/** + * Verification API requests related models + * + * @since 1.0 + */ +package com.sinch.sdk.domains.verification.models.requests; diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/response/package-info.java b/client/src/main/com/sinch/sdk/domains/verification/models/response/package-info.java new file mode 100644 index 00000000..7b16cb3f --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/response/package-info.java @@ -0,0 +1,6 @@ +/** + * Verification API responses related models + * + * @since 1.0 + */ +package com.sinch.sdk.domains.verification.models.response; diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationEvent.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationEvent.java new file mode 100644 index 00000000..8daf66a0 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationEvent.java @@ -0,0 +1,118 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.sinch.sdk.domains.verification.models.Identity; +import com.sinch.sdk.domains.verification.models.NumberIdentity; +import com.sinch.sdk.domains.verification.models.VerificationId; +import com.sinch.sdk.domains.verification.models.VerificationMethodType; +import com.sinch.sdk.domains.verification.models.VerificationReference; +import java.util.Optional; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "event", + visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = VerificationRequestEvent.class, name = "VerificationRequestEvent"), + @JsonSubTypes.Type(value = VerificationResultEvent.class, name = "VerificationResultEvent") +}) +public class VerificationEvent { + + private final VerificationId id; + private final String event; + private final VerificationMethodType method; + private final Identity identity; + private final VerificationReference reference; + private final String custom; + + /** + * Base class for verification event + * @param id The ID of the verification request. + * @param event The type of the event. + * @param method The verification method + * @param identity Specifies the type of endpoint that will be verified and the particular endpoint. number is currently the only supported endpoint type + * @param reference The reference ID that was optionally passed together with the verification request + * @param custom A custom string that can be provided during a verification request. + * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/ + * @since 1.0 + */ + @JsonCreator + VerificationEvent( + @JsonProperty("id") String id, + @JsonProperty("event") String event, + @JsonProperty("method") String method, + @JsonProperty("identity") jsonIdentity identity, + @JsonProperty("reference") String reference, + @JsonProperty("custom") String custom) { + this.id = VerificationId.valueOf(id); + this.event = event; + this.method = VerificationMethodType.from(method); + this.identity = NumberIdentity.builder().setEndpoint(identity.endpoint).build(); + if (null != reference) { + this.reference = VerificationReference.valueOf(reference); + } else { + this.reference = null; + } + this.custom = custom; + } + + public VerificationId getId() { + return id; + } + + public String getEvent() { + return event; + } + + public VerificationMethodType getMethod() { + return method; + } + + public Identity getIdentity() { + return identity; + } + + public Optional getReference() { + return Optional.ofNullable(reference); + } + + public Optional getCustom() { + return Optional.ofNullable(custom); + } + + @Override + public String toString() { + return "VerificationEvent{" + + "id=" + + id + + ", event='" + + event + + '\'' + + ", method=" + + method + + ", identity=" + + identity + + ", reference=" + + reference + + ", custom='" + + custom + + '\'' + + '}'; + } + + static class jsonIdentity { + String type; + String endpoint; + + @JsonCreator + public jsonIdentity( + @JsonProperty("type") String type, @JsonProperty("endpoint") String endpoint) { + this.type = type; + this.endpoint = endpoint; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.java new file mode 100644 index 00000000..9acddd7a --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.java @@ -0,0 +1,77 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sinch.sdk.domains.verification.models.Price; +import java.util.Collection; +import java.util.Optional; + +public class VerificationRequestEvent extends VerificationEvent { + + private final Price price; + private final Collection acceptLanguage; + + /** + *This event is a POST request to the specified verification callback URL and is triggered when a new verification request is made from the SDK client or the Verification Request API. This callback event is only triggered when a verification callback URL is specified in your dashboard. + * @param id The ID of the verification request. + * @param event The type of the event. + * @param method The verification method + * @param identity Specifies the type of endpoint that will be verified and the particular endpoint. number is currently the only supported endpoint type + * @param reference The reference ID that was optionally passed together with the verification request + * @param custom A custom string that can be provided during a verification request. + * @param price The amount of money and currency of the verification request + * @param acceptLanguage Allows you to set or override if provided in the API request, the SMS verification content language. Only used with the SMS verification method. The content language specified in the API request or in the callback can be overridden by carrier provider specific templates, due to compliance and legal requirements, such as US shortcode requirements + * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/#tag/Verification-callbacks/paths/VerificationRequestEvent/post + * @since 1.0 + */ + @JsonCreator + VerificationRequestEvent( + @JsonProperty("id") String id, + @JsonProperty("event") String event, + @JsonProperty("method") String method, + @JsonProperty("identity") jsonIdentity identity, + @JsonProperty("price") jsonPrice price, + @JsonProperty("reference") String reference, + @JsonProperty("custom") String custom, + @JsonProperty("acceptLanguage") Collection acceptLanguage) { + super(id, event, method, identity, reference, custom); + + if (null != price) { + this.price = Price.builder().setAmount(price.amount).setCurrencyId(price.currencyId).build(); + } else { + this.price = null; + } + this.acceptLanguage = acceptLanguage; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + public Optional> getAcceptLanguage() { + return Optional.ofNullable(acceptLanguage); + } + + @Override + public String toString() { + return "VerificationRequestEvent{" + + "price=" + + price + + ", acceptLanguage=" + + acceptLanguage + + "} " + + super.toString(); + } + + static class jsonPrice { + Float amount; + String currencyId; + + @JsonCreator + public jsonPrice( + @JsonProperty("amount") Float amount, @JsonProperty("currencyId") String currencyId) { + this.amount = amount; + this.currencyId = currencyId; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponse.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponse.java new file mode 100644 index 00000000..f00d4c8e --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponse.java @@ -0,0 +1,53 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Base class for verification callback response + * @since 1.0 + */ +public class VerificationResponse { + + @JsonProperty("action") + private final VerificationResponseActionType action; + + /** + * + * @param action Determines whether the verification can be executed + */ + VerificationResponse(VerificationResponseActionType action) { + this.action = action; + } + + public VerificationResponseActionType getAction() { + return action; + } + + @Override + public String toString() { + return "VerificationResponse{" + "action=" + action + '}'; + } + + public static Builder builder() { + return new Builder<>(); + } + + public static class Builder> { + + VerificationResponseActionType action; + + public B setAction(VerificationResponseActionType action) { + this.action = action; + return self(); + } + + public VerificationResponse build() { + return new VerificationResponse(action); + } + + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseActionType.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseActionType.java new file mode 100644 index 00000000..1e18ac5c --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseActionType.java @@ -0,0 +1,50 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.sinch.sdk.core.utils.EnumDynamic; +import com.sinch.sdk.core.utils.EnumSupportDynamic; +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * Determines whether the verification can be executed. + * See action type response documentation + * @since 1.0 + */ +public class VerificationResponseActionType + extends EnumDynamic { + + public static final VerificationResponseActionType ALLOW = + new VerificationResponseActionType("allow"); + + public static final VerificationResponseActionType DENY = + new VerificationResponseActionType("deny"); + + /** */ + private static final EnumSupportDynamic ENUM_SUPPORT = + new EnumSupportDynamic<>( + VerificationResponseActionType.class, + VerificationResponseActionType::new, + Arrays.asList(ALLOW, DENY)); + + private VerificationResponseActionType(String value) { + super(value); + } + + public static Stream values() { + return ENUM_SUPPORT.values(); + } + + public static VerificationResponseActionType from(String value) { + return ENUM_SUPPORT.from(value); + } + + public static String valueOf(VerificationResponseActionType e) { + return ENUM_SUPPORT.valueOf(e); + } + + @JsonValue + public String getValue() { + return this.value(); + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.java new file mode 100644 index 00000000..5b4082b4 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.java @@ -0,0 +1,127 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Verification response related to call out + */ +public class VerificationResponseCallout extends VerificationResponse { + + @JsonProperty("callout") + private final CalloutResponse callout; + + /** + * + * @param action Determines whether the verification can be executed. + * @param callout call out related information + */ + VerificationResponseCallout(VerificationResponseActionType action, CalloutResponse callout) { + super(action); + this.callout = callout; + } + + public CalloutResponse getCallout() { + return callout; + } + + @Override + public String toString() { + return "VerificationResponseCallout{" + "callout=" + callout + "} " + super.toString(); + } + + /** + * Call out related information for call out verification callback + * See callout response documentation + * @since 1.0 + */ + public static class CalloutResponse { + + @JsonProperty("code") + private final Integer code; + + @JsonProperty("speech") + private final SpeechResponse speech; + + /** + * + * @param code The Phone Call PIN that should be entered by the user. Sinch servers automatically generate PIN codes for Phone Call verification. If you want to set your own code, you can specify it in the response to the Verification Request Event. + * @param speech An object defining various properties for the text-to-speech message. + */ + public CalloutResponse(Integer code, SpeechResponse speech) { + this.code = code; + this.speech = speech; + } + + public Integer getCode() { + return code; + } + + public SpeechResponse getSpeech() { + return speech; + } + + @Override + public String toString() { + return "CalloutResponse{" + "code='" + code + '\'' + ", speech=" + speech + '}'; + } + } + + /** + * Speech related information for SMS verification callback + * See speech response documentation + * @since 1.0 + */ + public static class SpeechResponse { + + @JsonProperty("locale") + private final String locale; + + /** + * + * @param locale Indicates the language that should be used for the text-to-speech message. Currently, only en-US is supported. + */ + public SpeechResponse(String locale) { + this.locale = locale; + } + + public String getLocale() { + return locale; + } + + @Override + public String toString() { + return "SpeechResponse{" + "locale='" + locale + '\'' + '}'; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends VerificationResponse.Builder { + + Integer code; + + String locale; + + public Builder setCode(Integer code) { + this.code = code; + return self(); + } + + public Builder setLocale(String locale) { + this.locale = locale; + return self(); + } + + public VerificationResponseCallout build() { + return new VerificationResponseCallout( + action, new CalloutResponse(code, new SpeechResponse(locale))); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.java new file mode 100644 index 00000000..d88a5c3c --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.java @@ -0,0 +1,99 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Verification response related to flash call + */ +public class VerificationResponseFlashCall extends VerificationResponse { + + @JsonProperty("flashcall") + private final FlashCallResponse flashCall; + + /** + * + * @param action Determines whether the verification can be executed. + * @param flashCall Flash call related information + */ + VerificationResponseFlashCall( + VerificationResponseActionType action, FlashCallResponse flashCall) { + super(action); + this.flashCall = flashCall; + } + + public FlashCallResponse getFlashCall() { + return flashCall; + } + + @Override + public String toString() { + return "VerificationResponseFlashCall{" + "flashCall=" + flashCall + "} " + super.toString(); + } + + /** + * Flash call related information for flach call verification callback + * See callout response documentation + * @since 1.0 + */ + public static class FlashCallResponse { + + @JsonProperty("cli") + private final String cli; + + @JsonProperty("dialTimeout") + private final Integer dialTimeout; + + /** + * + * @param cli The phone number that will be displayed to the user when the flashcall is received on the user's phone. By default, the Sinch dashboard will randomly select the CLI that will be displayed during a flashcall from a pool of numbers. If you want to set your own CLI, you can specify it in the response to the Verification Request Event. + * @param dialTimeout The maximum time that a flashcall verification will be active and can be completed. If the phone number hasn't been verified successfully during this time, then the verification request will fail. By default, the Sinch dashboard will automatically optimize dial time out during a flashcall. If you want to set your own dial time out for the flashcall, you can specify it in the response to the Verification Request Event. + */ + public FlashCallResponse(String cli, Integer dialTimeout) { + this.cli = cli; + this.dialTimeout = dialTimeout; + } + + public String getCli() { + return cli; + } + + public Integer getDialTimeout() { + return dialTimeout; + } + + @Override + public String toString() { + return "FlashCallResponse{" + "cli='" + cli + '\'' + ", dialTimeout=" + dialTimeout + '}'; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends VerificationResponse.Builder { + + String cli; + + Integer dialTimeout; + + public Builder setCli(String cli) { + this.cli = cli; + return self(); + } + + public Builder setDialTimeout(Integer dialTimeout) { + this.dialTimeout = dialTimeout; + return self(); + } + + public VerificationResponseFlashCall build() { + return new VerificationResponseFlashCall(action, new FlashCallResponse(cli, dialTimeout)); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.java new file mode 100644 index 00000000..02bf30e9 --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.java @@ -0,0 +1,89 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collection; + +/** + * Verification response related to SMS + */ +public class VerificationResponseSMS extends VerificationResponse { + + @JsonProperty("sms") + private final SMSResponse sms; + + /** + * + * @param action Determines whether the verification can be executed. + * @param sms SMS related information + */ + VerificationResponseSMS(VerificationResponseActionType action, SMSResponse sms) { + super(action); + this.sms = sms; + } + + public SMSResponse getSms() { + return sms; + } + + @Override + public String toString() { + return "VerificationResponseSMS{" + "sms=" + sms + "} " + super.toString(); + } + + /** + * SMS related information for SMS verification callback + * See sms response documentation + * @since 1.0 + */ + public static class SMSResponse { + @JsonProperty("code") + private final Integer code; + + @JsonProperty("acceptLanguage") + private final Collection acceptLanguage; + + /** + * + * @param code The SMS PIN that should be used. By default, the Sinch dashboard will automatically generate PIN codes for SMS verification. If you want to set your own PIN, you can specify it in the response to the Verification Request Event. + * @param acceptLanguage The SMS verification content language. Set in the verification request. + */ + public SMSResponse(Integer code, Collection acceptLanguage) { + this.code = code; + this.acceptLanguage = acceptLanguage; + } + + @Override + public String toString() { + return "SMSResponse{" + "code='" + code + '\'' + ", acceptLanguage=" + acceptLanguage + '}'; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends VerificationResponse.Builder { + + Integer code; + Collection acceptLanguage; + + public Builder setCode(Integer code) { + this.code = code; + return self(); + } + + public Builder setAcceptLanguage(Collection acceptLanguage) { + this.acceptLanguage = acceptLanguage; + return self(); + } + + public VerificationResponseSMS build() { + return new VerificationResponseSMS(action, new SMSResponse(code, acceptLanguage)); + } + + @Override + protected Builder self() { + return this; + } + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.java new file mode 100644 index 00000000..686eb4cb --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.java @@ -0,0 +1,73 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sinch.sdk.domains.verification.models.VerificationReportReasonType; +import com.sinch.sdk.domains.verification.models.VerificationReportStatusType; +import com.sinch.sdk.domains.verification.models.VerificationSourceType; +import java.util.Optional; + +public class VerificationResultEvent extends VerificationEvent { + + private final VerificationReportStatusType status; + private final VerificationReportReasonType reason; + private final VerificationSourceType source; + + /** + * This event is a POST request to the specified verification callback URL and triggered when a verification has been completed and the result is known. It's used to report the verification result to the developer's backend application. This callback event is only triggered when the verification callback URL is specified in your dashboard. + * @param id The ID of the verification request. + * @param event The type of the event. + * @param method The verification method + * @param identity Specifies the type of endpoint that will be verified and the particular endpoint. number is currently the only supported endpoint type + * @param reference The reference ID that was optionally passed together with the verification request + * @param custom A custom string that can be provided during a verification request. + * @param status The status of the verification request + * @param reason Displays the reason why a verification has FAILED, was DENIED, or was ABORTED + * @param source Free text that the client is sending, used to show if the call/SMS was intercepted or not. + * see https://developers.sinch.com/docs/verification/api-reference/verification/tag/Verification-callbacks/#tag/Verification-callbacks/paths/VerificationResultEvent/post + * @since 1.0 + */ + @JsonCreator + VerificationResultEvent( + @JsonProperty("id") String id, + @JsonProperty("event") String event, + @JsonProperty("method") String method, + @JsonProperty("identity") jsonIdentity identity, + @JsonProperty("reference") String reference, + @JsonProperty("custom") String custom, + @JsonProperty("status") String status, + @JsonProperty("reason") String reason, + @JsonProperty("source") String source) { + super(id, event, method, identity, reference, custom); + + this.status = VerificationReportStatusType.from(status); + this.reason = null != reason ? VerificationReportReasonType.from(reason) : null; + this.source = null != source ? VerificationSourceType.from(source) : null; + } + + public VerificationReportStatusType getStatus() { + return status; + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + + public Optional getSource() { + return Optional.ofNullable(source); + } + + @Override + public String toString() { + return "VerificationResultEvent{" + + "status=" + + status + + ", reason=" + + reason + + ", source='" + + source + + '\'' + + "} " + + super.toString(); + } +} diff --git a/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/package-info.java b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/package-info.java new file mode 100644 index 00000000..6cc0c7fb --- /dev/null +++ b/client/src/main/com/sinch/sdk/domains/verification/models/webhooks/package-info.java @@ -0,0 +1,6 @@ +/** + * Verification API webhooks (callback) related models + * + * @since 1.0 + */ +package com.sinch.sdk.domains.verification.models.webhooks; diff --git a/client/src/main/com/sinch/sdk/models/Configuration.java b/client/src/main/com/sinch/sdk/models/Configuration.java index 35f8b777..21b9d164 100644 --- a/client/src/main/com/sinch/sdk/models/Configuration.java +++ b/client/src/main/com/sinch/sdk/models/Configuration.java @@ -2,7 +2,9 @@ import com.sinch.sdk.core.models.ServerConfiguration; -/** Configuration used by Sinch Client */ +/** + * Configuration used by Sinch Client + */ public class Configuration { private final String keyId; @@ -13,6 +15,9 @@ public class Configuration { private final SMSRegion smsRegion; private final String smsUrl; private final String verificationUrl; + private final String applicationKey; + private final String applicationSecret; + private Configuration( String keyId, @@ -22,7 +27,9 @@ private Configuration( String numbersUrl, SMSRegion smsRegion, String smsUrl, - String verificationUrl) { + String verificationUrl, + String applicationKey, + String applicationSecret) { this.keyId = keyId; this.keySecret = keySecret; this.projectId = projectId; @@ -31,6 +38,8 @@ private Configuration( this.smsRegion = null == smsRegion ? SMSRegion.US : smsRegion; this.smsUrl = smsUrl; this.verificationUrl = verificationUrl; + this.applicationKey = applicationKey; + this.applicationSecret = applicationSecret; } @Override @@ -146,7 +155,7 @@ public ServerConfiguration getSmsServer() { * * @return SMS region * @see https://developers.sinch.com/docs/sms/api-reference/#base-url/ + * href="https://developers.sinch.com/docs/sms/api-reference/#base-url/">https://developers.sinch.com/docs/sms/api-reference/#base-url/ * @since 1.0 */ public SMSRegion getSmsRegion() { @@ -172,6 +181,7 @@ public String getSmsUrl() { public ServerConfiguration getVerificationServer() { return new ServerConfiguration(getVerificationUrl()); } + /** * Verification URL * @@ -182,6 +192,38 @@ public String getVerificationUrl() { return verificationUrl; } + /** + * Application key to be used for Verification and Voice services + *

+ * Use application secret in place of unified configuration for authentication (see Sinch + * dashboard for details) These credentials are related to Verification & Voice Apps + * + * @return Application key + * @see Sinch + * Documentation + * @since 1.0 + */ + public String getApplicationKey() { + return applicationKey; + } + + /** + * Application secret to be used for Verification and Voice services + *

+ * Use application secret in place of unified configuration for authentication (see Sinch + * dashboard for details) These credentials are related to Verification & Voice Apps + * + * @return Application key + * @see Sinch + * Documentation + * @since 1.0 + */ + public String getApplicationSecret() { + return applicationSecret; + } + public static Builder builder() { return new Builder(); } @@ -190,7 +232,9 @@ public static Builder builder(Configuration configuration) { return new Builder(configuration); } - /** Configuration builder */ + /** + * Configuration builder + */ public static class Builder { private String keyId; @@ -201,8 +245,11 @@ public static class Builder { private SMSRegion smsRegion; private String smsUrl; private String verificationUrl; + private String applicationKey; + private String applicationSecret; - protected Builder() {} + protected Builder() { + } /** * Initialize a builder with existing configuration @@ -219,6 +266,8 @@ protected Builder(Configuration configuration) { this.smsRegion = configuration.getSmsRegion(); this.smsUrl = configuration.getSmsUrl(); this.verificationUrl = configuration.getVerificationUrl(); + this.applicationKey = configuration.getApplicationKey(); + this.applicationSecret = configuration.getApplicationSecret(); } /** @@ -229,7 +278,8 @@ protected Builder(Configuration configuration) { */ public Configuration build() { return new Configuration( - keyId, keySecret, projectId, oauthUrl, numbersUrl, smsRegion, smsUrl, verificationUrl); + keyId, keySecret, projectId, oauthUrl, numbersUrl, smsRegion, smsUrl, verificationUrl, + applicationKey, applicationSecret); } /** @@ -327,5 +377,29 @@ public Builder setVerificationUrl(String verificationUrl) { this.verificationUrl = verificationUrl; return this; } + + /** + * Set Application secret + * + * @param applicationKey Application key to be used + * @return Current builder + * @since 1.0 + */ + public Builder setApplicationKey(String applicationKey) { + this.applicationKey = applicationKey; + return this; + } + + /** + * Set Application secret + * + * @param applicationSecret Application secret to be used + * @return Current builder + * @since 1.0 + */ + public Builder setApplicationSecret(String applicationSecret) { + this.applicationSecret = applicationSecret; + return this; + } } } diff --git a/client/src/main/com/sinch/sdk/package-info.java b/client/src/main/com/sinch/sdk/package-info.java index 1da0e66f..d930893d 100644 --- a/client/src/main/com/sinch/sdk/package-info.java +++ b/client/src/main/com/sinch/sdk/package-info.java @@ -1,5 +1,5 @@ /** - * Sinch Java SDK for Numbers & SMS + * Sinch Java SDK for Numbers, SMS & Verification * *

Provides the client necessary to interface with Sinch APIS * diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/StatusServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/StatusServiceTest.java index c38b25d4..95179d60 100644 --- a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/StatusServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/StatusServiceTest.java @@ -20,7 +20,6 @@ import com.sinch.sdk.domains.verification.models.dto.v1.VerificationReportDtoTest; import com.sinch.sdk.models.Configuration; import java.util.Map; -import java.util.function.Supplier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,13 +31,13 @@ public class StatusServiceTest extends BaseTest { @Mock QueryVerificationsApi api; @Mock Configuration configuration; @Mock HttpClient httpClient; - @Mock Supplier> authManagerSupplier; + @Mock Map authManagers; StatusService service; @BeforeEach public void initMocks() { - service = spy(new StatusService(configuration, httpClient, authManagerSupplier)); + service = spy(new StatusService(configuration, httpClient, authManagers)); doReturn(api).when(service).getApi(); } diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/VerificationsServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/VerificationsServiceTest.java index f6d7c54e..2040a23b 100644 --- a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/VerificationsServiceTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/VerificationsServiceTest.java @@ -23,7 +23,6 @@ import com.sinch.sdk.domains.verification.models.response.StartVerificationResponse; import com.sinch.sdk.models.Configuration; import java.util.Map; -import java.util.function.Supplier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,13 +40,13 @@ public class VerificationsServiceTest extends BaseTest { @Mock SendingAndReportingVerificationsApi api; @Mock Configuration configuration; @Mock HttpClient httpClient; - @Mock Supplier> authManagerSupplier; + @Mock Map authManagers; VerificationsService service; @BeforeEach public void initMocks() { - service = spy(new VerificationsService(configuration, httpClient, authManagerSupplier)); + service = spy(new VerificationsService(configuration, httpClient, authManagers)); doReturn(api).when(service).getApi(); } diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/WebhooksServiceTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/WebhooksServiceTest.java new file mode 100644 index 00000000..5a4fcc72 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/WebhooksServiceTest.java @@ -0,0 +1,88 @@ +package com.sinch.sdk.domains.verification.adapters; + +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.BaseTest; +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.verification.WebHooksService; +import com.sinch.sdk.models.Configuration; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@TestWithResources +public class WebhooksServiceTest extends BaseTest { + + public String request = + "{\"id\":\"018c25e1-7163-5677-b8fb-467d7b1cffa5\",\"event\":\"VerificationResultEvent\",\"method\":\"sms\",\"identity\":{\"verified\":false,\"type\":\"number\",\"endpoint\":\"+33628254417\"},\"status\":\"FAIL\",\"reason\":\"Expired\"}"; + + WebHooksService webHooksService; + + @Test + void checkApplicationAuthentication() throws ApiException { + + Map headers = + Stream.of( + new AbstractMap.SimpleEntry<>( + "authorization", + "application 789:xfKhO0XvlRNJraahUBEJzzi1f3Fn3pYO41/ZzwOHPaQ="), + new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), + new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + boolean authenticationResult = + webHooksService.checkAuthentication("POST", "/VerificationRequestEvent", headers, request); + + Assertions.assertThat(authenticationResult).isEqualTo(true); + } + + @Test + void checkApplicationAuthenticationFailureOnKey() throws ApiException { + + Map headers = + Stream.of( + new AbstractMap.SimpleEntry<>("authorization", "application badkey:xfKhO0XvlRNJraahUBEJzzi1f3Fn3pYO41/ZzwOHPaQ="), + new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), + new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + boolean authenticationResult = + webHooksService.checkAuthentication("POST", "/VerificationRequestEvent", headers, request); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + @Test + void checkApplicationAuthenticationFailureOnHash() throws ApiException { + + Map headers = + Stream.of( + new AbstractMap.SimpleEntry<>("authorization", "application 789:fooHash="), + new AbstractMap.SimpleEntry<>("content-type", "application/json; charset=utf-8"), + new AbstractMap.SimpleEntry<>("x-timestamp", "2023-12-01T15:01:20.0406449Z")) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + boolean authenticationResult = + webHooksService.checkAuthentication("POST", "/VerificationRequestEvent", headers, request); + + Assertions.assertThat(authenticationResult).isEqualTo(false); + } + + @BeforeEach + public void setUp() throws IOException { + + Configuration configuration = Configuration.builder() + .setProjectId("unused") + .setKeyId("unused") + .setKeySecret("unused") + .setApplicationKey("789") + .setApplicationSecret("9876543210") + .build(); + + webHooksService = new SinchClient(configuration).verification().webhooks(); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverterTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverterTest.java index dc8d333a..73920436 100644 --- a/client/src/test/java/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverterTest.java +++ b/client/src/test/java/com/sinch/sdk/domains/verification/adapters/converters/VerificationsDtoConverterTest.java @@ -11,6 +11,7 @@ import com.sinch.sdk.domains.verification.models.VerificationReportReasonType; import com.sinch.sdk.domains.verification.models.VerificationReportSMS; import com.sinch.sdk.domains.verification.models.VerificationReportStatusType; +import com.sinch.sdk.domains.verification.models.VerificationSourceType; import com.sinch.sdk.domains.verification.models.dto.v1.StartVerificationRequestDtoTest; import com.sinch.sdk.domains.verification.models.dto.v1.StartVerificationResponseDtoTest; import com.sinch.sdk.domains.verification.models.dto.v1.VerificationReportDtoTest; @@ -206,7 +207,7 @@ void convertReportCalloutResponse() { .setStatus(VerificationReportStatusType.FAIL) .setReason(VerificationReportReasonType.FRAUD) .setReference(VerificationReference.valueOf("my reference")) - .setSource("my source") + .setSource(VerificationSourceType.MANUAL) .build(); @Test @@ -239,7 +240,7 @@ void convertReportFlashCallResponse() { .setStatus(VerificationReportStatusType.FAIL) .setReason(VerificationReportReasonType.FRAUD) .setReference(VerificationReference.valueOf("my reference")) - .setSource("my source") + .setSource(VerificationSourceType.INTERCEPTED) .setVerificationPrice( Price.builder() .setCurrencyId("verificationPrice currency id") diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEventTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEventTest.java new file mode 100644 index 00000000..18e9d753 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEventTest.java @@ -0,0 +1,70 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.adelean.inject.resources.junit.jupiter.GivenJsonResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.domains.verification.models.NumberIdentity; +import com.sinch.sdk.domains.verification.models.Price; +import com.sinch.sdk.domains.verification.models.VerificationId; +import com.sinch.sdk.domains.verification.models.VerificationMethodType; +import com.sinch.sdk.domains.verification.models.VerificationReference; +import java.util.Collections; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +@TestWithResources +class VerificationRequestEventTest { + + @GivenJsonResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.json") + static VerificationRequestEvent dto; + + @Test + void getId() { + Assertions.assertThat(dto.getId()) + .usingRecursiveComparison() + .isEqualTo(VerificationId.valueOf("1234567890")); + } + + @Test + void getEvent() { + Assertions.assertThat(dto.getEvent()).isEqualTo("VerificationRequestEvent"); + } + + @Test + void getMethod() { + Assertions.assertThat(dto.getMethod()).isEqualTo(VerificationMethodType.from("sms")); + } + + @Test + void getIdentity() { + Assertions.assertThat(dto.getIdentity()) + .usingRecursiveComparison() + .isEqualTo(NumberIdentity.builder().setEndpoint("+11235551234").build()); + } + + @Test + void getReference() { + Assertions.assertThat(dto.getReference().get()) + .usingRecursiveComparison() + .isEqualTo(VerificationReference.valueOf("reference string")); + } + + @Test + void getCustom() { + Assertions.assertThat(dto.getCustom().get()).isEqualTo("custom string"); + } + + @Test + void getPrice() { + Assertions.assertThat(dto.getPrice().get()) + .usingRecursiveComparison() + .isEqualTo(Price.builder().setAmount(10.5F).setCurrencyId("USD").build()); + } + + @Test + void getAcceptLanguage() { + Assertions.assertThat(dto.getAcceptLanguage().get()) + .usingRecursiveComparison() + .isEqualTo(Collections.singletonList("es-ES")); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseTest.java new file mode 100644 index 00000000..77b06e20 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResponseTest.java @@ -0,0 +1,193 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.adelean.inject.resources.junit.jupiter.GivenTextResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.sinch.sdk.BaseTest; +import java.util.Collections; +import org.json.JSONException; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +@TestWithResources +class VerificationResponseTest extends BaseTest { + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.json") + String jsonResponseSMS; + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMSEmptySMS.json") + String jsonResponseSMSEmptySMS; + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.json") + String jsonResponseFlashCall; + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCallEmptyFlashCall.json") + String jsonResponseFlashCallEmptyFlashCall; + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.json") + String jsonResponseCallout; + + @GivenTextResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCalloutEmptyCallout.json") + String jsonResponseCalloutEmptyCallout; + + @Test + void serializeSMSResponse() throws JsonProcessingException, JSONException { + + VerificationResponseSMS value = + VerificationResponseSMS.builder() + .setAction(VerificationResponseActionType.DENY) + .setCode(5666) + .setAcceptLanguage(Collections.singletonList("a language")) + .build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseSMS, serializedString, true); + } + + @Test + void serializeSMSResponseEmptySms() throws JsonProcessingException, JSONException { + + VerificationResponseSMS value = + VerificationResponseSMS.builder().setAction(VerificationResponseActionType.ALLOW).build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseSMSEmptySMS, serializedString, true); + } + + @Test + void serializeSMS() throws JsonProcessingException, JSONException { + + VerificationResponseSMS.SMSResponse value = + new VerificationResponseSMS.SMSResponse(7890, Collections.singletonList("a language")); + + String expectedJSON = "{\"code\": 7890, \"acceptLanguage\":[\"a language\"]}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } + + @Test + void serializeEmptySMS() throws JsonProcessingException, JSONException { + + VerificationResponseSMS.SMSResponse value = new VerificationResponseSMS.SMSResponse(null, null); + + String expectedJSON = "{}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } + + @Test + void serializeFlashCallResponse() throws JsonProcessingException, JSONException { + + VerificationResponseFlashCall value = + VerificationResponseFlashCall.builder() + .setAction(VerificationResponseActionType.DENY) + .setCli("cli code") + .setDialTimeout(123) + .build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseFlashCall, serializedString, true); + } + + @Test + void serializeFlashCallResponseEmptyFlashCall() throws JsonProcessingException, JSONException { + + VerificationResponseFlashCall value = + VerificationResponseFlashCall.builder() + .setAction(VerificationResponseActionType.ALLOW) + .build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseFlashCallEmptyFlashCall, serializedString, true); + } + + @Test + void serializeFlashCall() throws JsonProcessingException, JSONException { + + VerificationResponseFlashCall.FlashCallResponse value = + new VerificationResponseFlashCall.FlashCallResponse("cli code", 123); + + String expectedJSON = "{\"cli\": \"cli code\", \"dialTimeout\":123}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } + + @Test + void serializeEmptyFlashCall() throws JsonProcessingException, JSONException { + + VerificationResponseFlashCall.FlashCallResponse value = + new VerificationResponseFlashCall.FlashCallResponse(null, null); + + String expectedJSON = "{}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } + + @Test + void serializeCalloutResponse() throws JsonProcessingException, JSONException { + + VerificationResponseCallout value = + VerificationResponseCallout.builder() + .setAction(VerificationResponseActionType.DENY) + .setCode(4567) + .setLocale("the locale") + .build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseCallout, serializedString, true); + } + + @Test + void serializeCalloutResponseEmptyCallout() throws JsonProcessingException, JSONException { + + VerificationResponseCallout value = + VerificationResponseCallout.builder() + .setAction(VerificationResponseActionType.ALLOW) + .setLocale(null) + .build(); + + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(jsonResponseCalloutEmptyCallout, serializedString, true); + } + + @Test + void serializeCallout() throws JsonProcessingException, JSONException { + + VerificationResponseCallout.CalloutResponse value = + new VerificationResponseCallout.CalloutResponse( + 1230, new VerificationResponseCallout.SpeechResponse("the locale")); + + String expectedJSON = "{\"code\": 1230, \"speech\":{\"locale\": \"the locale\"}}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } + + @Test + void serializeEmptyCallout() throws JsonProcessingException, JSONException { + + VerificationResponseCallout.CalloutResponse value = + new VerificationResponseCallout.CalloutResponse(null, null); + + String expectedJSON = "{}"; + String serializedString = objectMapper.writeValueAsString(value); + + JSONAssert.assertEquals(expectedJSON, serializedString, true); + } +} diff --git a/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEventTest.java b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEventTest.java new file mode 100644 index 00000000..adef34f5 --- /dev/null +++ b/client/src/test/java/com/sinch/sdk/domains/verification/models/webhooks/VerificationResultEventTest.java @@ -0,0 +1,72 @@ +package com.sinch.sdk.domains.verification.models.webhooks; + +import com.adelean.inject.resources.junit.jupiter.GivenJsonResource; +import com.adelean.inject.resources.junit.jupiter.TestWithResources; +import com.sinch.sdk.domains.verification.models.NumberIdentity; +import com.sinch.sdk.domains.verification.models.VerificationId; +import com.sinch.sdk.domains.verification.models.VerificationMethodType; +import com.sinch.sdk.domains.verification.models.VerificationReference; +import com.sinch.sdk.domains.verification.models.VerificationReportReasonType; +import com.sinch.sdk.domains.verification.models.VerificationReportStatusType; +import com.sinch.sdk.domains.verification.models.VerificationSourceType; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +@TestWithResources +class VerificationResultEventTest { + + @GivenJsonResource( + "/client/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.json") + static VerificationResultEvent dto; + + @Test + void getId() { + Assertions.assertThat(dto.getId()) + .usingRecursiveComparison() + .isEqualTo(VerificationId.valueOf("1234567890")); + } + + @Test + void getEvent() { + Assertions.assertThat(dto.getEvent()).isEqualTo("VerificationResultEvent"); + } + + @Test + void getMethod() { + Assertions.assertThat(dto.getMethod()).isEqualTo(VerificationMethodType.from("sms")); + } + + @Test + void getIdentity() { + Assertions.assertThat(dto.getIdentity()) + .usingRecursiveComparison() + .isEqualTo(NumberIdentity.builder().setEndpoint("+11235551234").build()); + } + + @Test + void getReference() { + Assertions.assertThat(dto.getReference().get()) + .usingRecursiveComparison() + .isEqualTo(VerificationReference.valueOf("reference string")); + } + + @Test + void getCustom() { + Assertions.assertThat(dto.getCustom().get()).isEqualTo("custom string"); + } + + @Test + void getStatus() { + Assertions.assertThat(dto.getStatus()).isEqualTo(VerificationReportStatusType.DENIED); + } + + @Test + void getReason() { + Assertions.assertThat(dto.getReason().get()).isEqualTo(VerificationReportReasonType.FRAUD); + } + + @Test + void getSource() { + Assertions.assertThat(dto.getSource().get()).isEqualTo(VerificationSourceType.MANUAL); + } +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.json new file mode 100644 index 00000000..03de0b1e --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationRequestEvent.json @@ -0,0 +1,18 @@ +{ + "id": "1234567890", + "event": "VerificationRequestEvent", + "method": "sms", + "identity": { + "type": "number", + "endpoint": "+11235551234" + }, + "price": { + "amount": 10.5, + "currencyId": "USD" + }, + "reference": "reference string", + "custom": "custom string", + "acceptLanguage": [ + "es-ES" + ] +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.json new file mode 100644 index 00000000..4671d560 --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCallout.json @@ -0,0 +1,9 @@ +{ + "callout": { + "code": 4567, + "speech": { + "locale": "the locale" + } + }, + "action": "deny" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCalloutEmptyCallout.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCalloutEmptyCallout.json new file mode 100644 index 00000000..875603fc --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseCalloutEmptyCallout.json @@ -0,0 +1,6 @@ +{ + "callout": { + "speech": {} + }, + "action": "allow" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.json new file mode 100644 index 00000000..caa4a2a8 --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCall.json @@ -0,0 +1,7 @@ +{ + "flashcall": { + "cli": "cli code", + "dialTimeout": 123 + }, + "action": "deny" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCallEmptyFlashCall.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCallEmptyFlashCall.json new file mode 100644 index 00000000..5dc2731e --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseFlashCallEmptyFlashCall.json @@ -0,0 +1,4 @@ +{ + "flashcall": {}, + "action": "allow" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.json new file mode 100644 index 00000000..90d56729 --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMS.json @@ -0,0 +1,9 @@ +{ + "sms": { + "code": 5666, + "acceptLanguage": [ + "a language" + ] + }, + "action": "deny" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMSEmptySMS.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMSEmptySMS.json new file mode 100644 index 00000000..b553ea18 --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResponseSMSEmptySMS.json @@ -0,0 +1,4 @@ +{ + "sms": {}, + "action": "allow" +} diff --git a/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.json b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.json new file mode 100644 index 00000000..d2582a58 --- /dev/null +++ b/client/src/test/resources/client/sinch/sdk/domains/verification/models/webhooks/VerificationResultEvent.json @@ -0,0 +1,14 @@ +{ + "id": "1234567890", + "event": "VerificationResultEvent", + "method": "sms", + "identity": { + "type": "number", + "endpoint": "+11235551234" + }, + "reference": "reference string", + "custom": "custom string", + "source": "manual", + "status": "DENIED", + "reason": "Fraud" +} diff --git a/core/src/main/com/sinch/sdk/core/http/AuthManager.java b/core/src/main/com/sinch/sdk/core/http/AuthManager.java index 02429cca..64150669 100644 --- a/core/src/main/com/sinch/sdk/core/http/AuthManager.java +++ b/core/src/main/com/sinch/sdk/core/http/AuthManager.java @@ -1,6 +1,7 @@ package com.sinch.sdk.core.http; import com.sinch.sdk.core.utils.Pair; +import java.time.Instant; import java.util.Collection; public interface AuthManager { @@ -13,6 +14,11 @@ public interface AuthManager { void resetToken(); + default Collection> getAuthorizationHeaders( + String method, String httpContentType, String path, String body) { + return getAuthorizationHeaders(Instant.now().toString(), method, httpContentType, path, body); + } + Collection> getAuthorizationHeaders( - String method, String httpContentType, String path, String body); + String timestamp, String method, String httpContentType, String path, String body); } diff --git a/openapi-contracts/src/test/java/com/sinch/sdk/domains/verification/models/dto/v1/VerificationReportDtoTest.java b/openapi-contracts/src/test/java/com/sinch/sdk/domains/verification/models/dto/v1/VerificationReportDtoTest.java index 78441aba..c54028ff 100644 --- a/openapi-contracts/src/test/java/com/sinch/sdk/domains/verification/models/dto/v1/VerificationReportDtoTest.java +++ b/openapi-contracts/src/test/java/com/sinch/sdk/domains/verification/models/dto/v1/VerificationReportDtoTest.java @@ -45,7 +45,7 @@ public class VerificationReportDtoTest extends BaseTest { .status("FAIL") .reason("Fraud") .reference("my reference") - .source("my source"); + .source("manual"); @GivenJsonResource("/domains/verification/v1/VerificationReportSMSResponseDto.json") VerificationResponseDto loadedVerificationSMSDto; @@ -57,7 +57,7 @@ public class VerificationReportDtoTest extends BaseTest { .status("FAIL") .reason("Fraud") .reference("my reference") - .source("my source") + .source("intercepted") .price( new VerificationResponsePriceDto( new VerificationPriceInformationDto() diff --git a/pom.xml b/pom.xml index 58f69c9a..51b4bded 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,7 @@ + ${project.name} public core/src/main; client/src/main; @@ -128,6 +129,10 @@ **/AbstractOpenApiSchema.java + + Sinch Client + com.sinch.sdk* + Core package com.sinch.sdk.core* @@ -141,8 +146,8 @@ com.sinch.sdk.domains.sms* - Sinch Client - com.sinch.sdk* + Verification + com.sinch.sdk.domains.verification* @@ -270,7 +275,9 @@ **/*.java - + + **/*.java + 1.8 diff --git a/sample-app/README.md b/sample-app/README.md index d1d947fd..bc82093b 100644 --- a/sample-app/README.md +++ b/sample-app/README.md @@ -4,6 +4,13 @@ This directory contains samples related to supported services/endpoint for Sinch It is not a submodule to mimic a real application to be built from a deployed SDK; this mean the SDK jar must be "installed" locally to be resolved as application dependency. +# Prerequisites + +- JDK 21 or later (Sinch SDK Java is requiring java 8 only but samples are requiring Java 21 JVM) +- [Maven](https://maven.apache.org/) +- [Maven Central](https://mvnrepository.com/artifact/com.sinch.sdk/sinch-sdk-java) +- [Sinch account](https://dashboard.sinch.com) + ## Usage 1. Generate and install locally the Sinch SDK java @@ -54,46 +61,79 @@ A full application chaining calls to Numbers service to onboard onto Java SDK an ### Dedicated service feature samples -|--------------|----------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| -| API | Service | Sample | Class | Notes | -|--------------|----------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| -| Numbers | Available | - CheckAvailability | [com.sinch.sample.numbers.available.CheckAvailability](src/main/java/com/sinch/sample/numbers/available/CheckAvailability.java) | Require `PHONE_NUMBER` parameter | -| | | - List | [com.sinch.sample.numbers.available.List](src/main/java/com/sinch/sample/numbers/available/List.java) | | -| | | - Rent | [com.sinch.sample.numbers.available.Rent](src/main/java/com/sinch/sample/numbers/available/Rent.java) | Require `PHONE_NUMBER` parameter | -| | | - RentAny | [com.sinch.sample.numbers.available.RentAny](src/main/java/com/sinch/sample/numbers/available/RentAny.java) | | -| | Active | - Get | [com.sinch.sample.numbers.active.Get](src/main/java/com/sinch/sample/numbers/active/Get.java) | Require `PHONE_NUMBER` parameter | -| | | - List | [com.sinch.sample.numbers.active.List](src/main/java/com/sinch/sample/numbers/active/List.java) | | -| | | - Release | [com.sinch.sample.numbers.active.Release](src/main/java/com/sinch/sample/numbers/active/Release.java) | Require `PHONE_NUMBER` parameter | -| | | - Update | [com.sinch.sample.numbers.active.Update](src/main/java/com/sinch/sample/numbers/active/Update.java) | Require `PHONE_NUMBER` parameter | -| | Callback | - Get | [com.sinch.sample.numbers.callback.Get](src/main/java/com/sinch/sample/numbers/callback/Get.java) | | -| | | - Update | [com.sinch.sample.numbers.callback.Update](src/main/java/com/sinch/sample/numbers/callback/Get.java) | | -| | Regions | - ListAll | [com.sinch.sample.numbers.regions.List](src/main/java/com/sinch/sample/numbers/regions/List.java) | | -| SMS | Batches | - Get | [com.sinch.sample.sms.batches.Get](src/main/java/com/sinch/sample/sms/batches/Get.java) | Require `BATCH_ID` parameter | -| | | - List | [com.sinch.sample.sms.batches.List](src/main/java/com/sinch/sample/sms/batches/List.java) | | -| | | - Send | [com.sinch.sample.sms.batches.Send](src/main/java/com/sinch/sample/sms/batches/Send.java) | | -| | | - Replace | [com.sinch.sample.sms.batches.Replace](src/main/java/com/sinch/sample/sms/batches/Replace.java) | Require `BATCH_ID` parameter | -| | | - Update | [com.sinch.sample.sms.batches.Update](src/main/java/com/sinch/sample/sms/batches/Update.java) | Require `BATCH_ID` parameter | -| | | - DryRun | [com.sinch.sample.sms.batches.DryRun](src/main/java/com/sinch/sample/sms/batches/dryRun.java) | | -| | | - Cancel | [com.sinch.sample.sms.batches.Cancel](src/main/java/com/sinch/sample/sms/batches/Cancel.java) | Require `BATCH_ID` parameter | -| | | - SendDeliveryFeedback | [com.sinch.sample.sms.batches.SendDeliveryFeedback](src/main/java/com/sinch/sample/sms/batches/SendDeliveryFeedback.java) | Require `BATCH_ID` parameter | -| | DeliveryReport | - Get | [com.sinch.sample.sms.deliveryReports.Get](src/main/java/com/sinch/sample/sms/deliveryReports/Get.java) | Require `BATCH_ID` parameter | -| | | - GetForNumber | [com.sinch.sample.sms.deliveryReports.GetForNumber](src/main/java/com/sinch/sample/sms/deliveryReports/GetForNumber.java) | Require `BATCH_ID` and `PHONE_NUMBER` parameters | -| | | - List | [com.sinch.sample.sms.deliveryReports.List](src/main/java/com/sinch/sample/sms/deliveryReports/List.java) | | -| | Groups | - Create | [com.sinch.sample.sms.groups.Create](src/main/java/com/sinch/sample/sms/groups/Create.java) | | -| | | - Get | [com.sinch.sample.sms.groups.Get](src/main/java/com/sinch/sample/sms/groups/Get.java) | | -| | | - Delete | [com.sinch.sample.sms.groups.Delete](src/main/java/com/sinch/sample/sms/groups/Delete.java) | | -| | | - List | [com.sinch.sample.sms.groups.List](src/main/java/com/sinch/sample/sms/groups/List.java) | | -| | | - ListMembers | [com.sinch.sample.sms.groups.ListMembers](src/main/java/com/sinch/sample/sms/groups/ListMembers.java) | | -| | | - Replace | [com.sinch.sample.sms.groups.Replace](src/main/java/com/sinch/sample/sms/groups/Replace.java) | | -| | | - Update | [com.sinch.sample.sms.groups.Update](src/main/java/com/sinch/sample/sms/groups/Update.java) | | -| | Inbounds | - Get | [com.sinch.sample.sms.inbounds.Get](src/main/java/com/sinch/sample/sms/inbounds/Get.java) | | -| | | - List | [com.sinch.sample.sms.inbounds.List](src/main/java/com/sinch/sample/sms/inbounds/List.java) | | -| | WebHooks | - DeliveryReport | [com.sinch.sample.sms.webhooks.DeliveryReport](src/main/java/com/sinch/sample/sms/webhooks/DeliveryReport.java) | | -| | | - IncomingSMSReport | [com.sinch.sample.sms.webhooks.IncomingSMS](src/main/java/com/sinch/sample/sms/webhooks/IncomingSMS.java) | | -| Verification | Report | - Start | [com.sinch.sample.verification.verifications.Start](src/main/java/com/sinch/sample/verification/verifications/Start.java) | | -| | | - GetReportById | [com.sinch.sample.verification.verifications.GetReportById](src/main/java/com/sinch/sample/verification/verifications/GetReportById.java) | | -| | | - GetReportByIdentity | [com.sinch.sample.verification.verifications.GetReportByIdentity](src/main/java/com/sinch/sample/verification/verifications/GetReportByIdentity.java) | | -| | Status | - GetById | [com.sinch.sample.verification.status.GetById](src/main/java/com/sinch/sample/verification/status/GetById.java) | | -| | | - GetByIdentity | [com.sinch.sample.verification.status.GetByIdentity](src/main/java/com/sinch/sample/verification/status/GetByIdentity.java) | | -| | | - GetByReference | [com.sinch.sample.verification.status.GetByReference](src/main/java/com/sinch/sample/verification/status/GetByReference.java) | | -|--------------|----------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| +#### Numbers + +| Service | Sample | Class | +|-----------|---------------------|---------------------------------------------------------------------------------------------------------------------------------| +| Available | - CheckAvailability | [com.sinch.sample.numbers.available.CheckAvailability](src/main/java/com/sinch/sample/numbers/available/CheckAvailability.java) | Require `PHONE_NUMBER` parameter | +| | - List | [com.sinch.sample.numbers.available.List](src/main/java/com/sinch/sample/numbers/available/List.java) | | +| | - Rent | [com.sinch.sample.numbers.available.Rent](src/main/java/com/sinch/sample/numbers/available/Rent.java) | Require `PHONE_NUMBER` parameter | +| | - RentAny | [com.sinch.sample.numbers.available.RentAny](src/main/java/com/sinch/sample/numbers/available/RentAny.java) | | +| Active | - Get | [com.sinch.sample.numbers.active.Get](src/main/java/com/sinch/sample/numbers/active/Get.java) | Require `PHONE_NUMBER` parameter | +| | - List | [com.sinch.sample.numbers.active.List](src/main/java/com/sinch/sample/numbers/active/List.java) | | +| | - Release | [com.sinch.sample.numbers.active.Release](src/main/java/com/sinch/sample/numbers/active/Release.java) | Require `PHONE_NUMBER` parameter | +| | - Update | [com.sinch.sample.numbers.active.Update](src/main/java/com/sinch/sample/numbers/active/Update.java) | Require `PHONE_NUMBER` parameter | +| Callback | - Get | [com.sinch.sample.numbers.callback.Get](src/main/java/com/sinch/sample/numbers/callback/Get.java) | | +| | - Update | [com.sinch.sample.numbers.callback.Update](src/main/java/com/sinch/sample/numbers/callback/Get.java) | | +| Regions | - ListAll | [com.sinch.sample.numbers.regions.List](src/main/java/com/sinch/sample/numbers/regions/List.java) | | + +#### SMS + +| Service | Sample | Class | +|----------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| Batches | - Get | [com.sinch.sample.sms.batches.Get](src/main/java/com/sinch/sample/sms/batches/Get.java) | Require `BATCH_ID` parameter | +| | - List | [com.sinch.sample.sms.batches.List](src/main/java/com/sinch/sample/sms/batches/List.java) | | +| | - Send | [com.sinch.sample.sms.batches.Send](src/main/java/com/sinch/sample/sms/batches/Send.java) | | +| | - Replace | [com.sinch.sample.sms.batches.Replace](src/main/java/com/sinch/sample/sms/batches/Replace.java) | Require `BATCH_ID` parameter | +| | - Update | [com.sinch.sample.sms.batches.Update](src/main/java/com/sinch/sample/sms/batches/Update.java) | Require `BATCH_ID` parameter | +| | - DryRun | [com.sinch.sample.sms.batches.DryRun](src/main/java/com/sinch/sample/sms/batches/dryRun.java) | | +| | - Cancel | [com.sinch.sample.sms.batches.Cancel](src/main/java/com/sinch/sample/sms/batches/Cancel.java) | Require `BATCH_ID` parameter | +| | - SendDeliveryFeedback | [com.sinch.sample.sms.batches.SendDeliveryFeedback](src/main/java/com/sinch/sample/sms/batches/SendDeliveryFeedback.java) | Require `BATCH_ID` parameter | +| DeliveryReport | - Get | [com.sinch.sample.sms.deliveryReports.Get](src/main/java/com/sinch/sample/sms/deliveryReports/Get.java) | Require `BATCH_ID` parameter | +| | - GetForNumber | [com.sinch.sample.sms.deliveryReports.GetForNumber](src/main/java/com/sinch/sample/sms/deliveryReports/GetForNumber.java) | Require `BATCH_ID` and `PHONE_NUMBER` parameters | +| | - List | [com.sinch.sample.sms.deliveryReports.List](src/main/java/com/sinch/sample/sms/deliveryReports/List.java) | | +| Groups | - Create | [com.sinch.sample.sms.groups.Create](src/main/java/com/sinch/sample/sms/groups/Create.java) | | +| | - Get | [com.sinch.sample.sms.groups.Get](src/main/java/com/sinch/sample/sms/groups/Get.java) | | +| | - Delete | [com.sinch.sample.sms.groups.Delete](src/main/java/com/sinch/sample/sms/groups/Delete.java) | | +| | - List | [com.sinch.sample.sms.groups.List](src/main/java/com/sinch/sample/sms/groups/List.java) | | +| | - ListMembers | [com.sinch.sample.sms.groups.ListMembers](src/main/java/com/sinch/sample/sms/groups/ListMembers.java) | | +| | - Replace | [com.sinch.sample.sms.groups.Replace](src/main/java/com/sinch/sample/sms/groups/Replace.java) | | +| | - Update | [com.sinch.sample.sms.groups.Update](src/main/java/com/sinch/sample/sms/groups/Update.java) | | +| Inbounds | - Get | [com.sinch.sample.sms.inbounds.Get](src/main/java/com/sinch/sample/sms/inbounds/Get.java) | | +| | - List | [com.sinch.sample.sms.inbounds.List](src/main/java/com/sinch/sample/sms/inbounds/List.java) | | +| WebHooks | - DeliveryReport | [com.sinch.sample.sms.webhooks.DeliveryReport](src/main/java/com/sinch/sample/sms/webhooks/DeliveryReport.java) | | +| | - IncomingSMSReport | [com.sinch.sample.sms.webhooks.IncomingSMS](src/main/java/com/sinch/sample/sms/webhooks/IncomingSMS.java) | | + +#### Verification + +| Service | Sample | Class | +|---------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| Report | - Start | [com.sinch.sample.verification.verifications.Start](src/main/java/com/sinch/sample/verification/verifications/Start.java) | | +| | - ReportById | [com.sinch.sample.verification.verifications.ReportById](src/main/java/com/sinch/sample/verification/verifications/ReportById.java) | | +| | - ReportByIdentity | [com.sinch.sample.verification.verifications.ReportByIdentity](src/main/java/com/sinch/sample/verification/verifications/ReportByIdentity.java) | | +| Status | - GetById | [com.sinch.sample.verification.status.GetById](src/main/java/com/sinch/sample/verification/status/GetById.java) | | +| | - GetByIdentity | [com.sinch.sample.verification.status.GetByIdentity](src/main/java/com/sinch/sample/verification/status/GetByIdentity.java) | | +| | - GetByReference | [com.sinch.sample.verification.status.GetByReference](src/main/java/com/sinch/sample/verification/status/GetByReference.java) | | + +### Dedicated webhooks feature samples +#### How to run webhooks samples +Webhooks samples are based onto dedicated SpringBoot applications. +By using service like `ngrok` and running locally the SpringBoot application you'll be able to use the local springboot application to response to callbacks defined within your dashboard +1. Install `ngrok` and launch it (see [ngrok site](https://ngrok.com/docs)) +2. Run the application: `mvn -f pom-webhooks.xml clean package spring-boot:run` +3. Configure your `dashboard` & `application settings` to define callback according to ngrok running `bridge` + - ngrok output put like `Forwarding https://35d0-78-117-86-140.ngrok.io -> http://localhost:8080` + - Verification webhooks application is having a controller responding to `/VerificationEvent` path + - Define dashboard callback value to `https://35d0-78-117-86-140.ngrok.io/VerificationEvent` +4. Execute a Verification action and check the webhook calls + +#### Verification WebHooks +Require to set following parameters (by environment or config file): +- `APPLICATION_API_KEY` +-` APPLICATION_API_SECRET` + +Check your dashboard to retrieve Application Credentials values + +| API | Sample | Class | Notes | +|--------------|------------------------|---------------------------------------------------------------------------------------------------------------------------|-------| +| Verification | Springboot application | [com.sinch.sample.webhooks.VerificationApplication](src/main/java/com/sinch/sample/webhooks/VerificationApplication.java) | | diff --git a/sample-app/pom-webhooks.xml b/sample-app/pom-webhooks.xml new file mode 100644 index 00000000..b811fa5a --- /dev/null +++ b/sample-app/pom-webhooks.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.sinch.sdk + sinch-sdk-java-sample-webhooks-app + 0.0.1-SNAPSHOT + Sinch Java SDK WebHooks Sample Application + Demo Project for Sinch webhooks + + + [0.0.0,) + 21 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-web + + + + com.sinch.sdk + sinch-sdk-java + ${sinch.sdk.java.version} + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/sample-app/pom.xml b/sample-app/pom.xml index 859bef44..0c0f5ef2 100644 --- a/sample-app/pom.xml +++ b/sample-app/pom.xml @@ -12,8 +12,8 @@ [0.0.0,) - 11 - 11 + 21 + 21 3.8.0 true true @@ -68,6 +68,19 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + -Xlint:all + + **/webhooks/** + + + + diff --git a/sample-app/src/main/java/com/sinch/sample/BaseApplication.java b/sample-app/src/main/java/com/sinch/sample/BaseApplication.java index 0bdff306..918836b4 100644 --- a/sample-app/src/main/java/com/sinch/sample/BaseApplication.java +++ b/sample-app/src/main/java/com/sinch/sample/BaseApplication.java @@ -12,10 +12,6 @@ public abstract class BaseApplication { protected static final Logger LOGGER = Utils.initializeLogger(BaseApplication.class.getName()); - // can super sed unified Sinch credentials if defined - private static final String VERIFICATION_API_KEY = "VERIFICATION_API_KEY"; - private static final String VERIFICATION_API_SECRET = "VERIFICATION_API_SECRET"; - protected SinchClient client; protected String phoneNumber; @@ -39,24 +35,6 @@ protected BaseApplication() throws IOException { client = new SinchClient(configuration); - handleVerificationCredentials(client, properties); - } - - void handleVerificationCredentials(SinchClient client, Properties props) { - - String verificationApiKey = - null != System.getenv(VERIFICATION_API_KEY) - ? System.getenv(VERIFICATION_API_KEY) - : props.getProperty(VERIFICATION_API_KEY); - String verificationApiSecret = - null != System.getenv(VERIFICATION_API_SECRET) - ? System.getenv(VERIFICATION_API_SECRET) - : props.getProperty(VERIFICATION_API_SECRET); - - // super-sed unified key/secret for verification API - if (null != verificationApiKey && null != verificationApiSecret) { - client.verification().setApplicationCredentials(verificationApiKey, verificationApiSecret); - } } public abstract void run(); diff --git a/sample-app/src/main/java/com/sinch/sample/Utils.java b/sample-app/src/main/java/com/sinch/sample/Utils.java index 51e1297a..4332e471 100644 --- a/sample-app/src/main/java/com/sinch/sample/Utils.java +++ b/sample-app/src/main/java/com/sinch/sample/Utils.java @@ -13,6 +13,10 @@ public class Utils { private static final String SINCH_KEY_SECRET = "SINCH_KEY_SECRET"; private static final String SINCH_PROJECT_ID = "SINCH_PROJECT_ID"; + // can super sed unified Sinch credentials if defined + private static final String APPLICATION_API_KEY = "APPLICATION_API_KEY"; + private static final String APPLICATION_API_SECRET = "APPLICATION_API_SECRET"; + public static Logger initializeLogger(String className) { try (InputStream logConfigInputStream = Utils.class.getClassLoader().getResourceAsStream("logging.properties")) { @@ -59,10 +63,22 @@ public static Configuration loadConfiguration(Logger logger) { ? System.getenv(SINCH_PROJECT_ID) : properties.getProperty(SINCH_PROJECT_ID); + String verificationApiKey = + null != System.getenv(APPLICATION_API_KEY) + ? System.getenv(APPLICATION_API_KEY) + : properties.getProperty(APPLICATION_API_KEY); + String verificationApiSecret = + null != System.getenv(APPLICATION_API_SECRET) + ? System.getenv(APPLICATION_API_SECRET) + : properties.getProperty(APPLICATION_API_SECRET); + return Configuration.builder() .setKeyId(keyId) .setKeySecret(keySecret) .setProjectId(projectId) + .setApplicationKey(verificationApiKey) + .setApplicationSecret(verificationApiSecret) .build(); } + } diff --git a/sample-app/src/main/java/com/sinch/sample/numbers/active/List.java b/sample-app/src/main/java/com/sinch/sample/numbers/active/List.java index 643b0f8d..40421b8a 100644 --- a/sample-app/src/main/java/com/sinch/sample/numbers/active/List.java +++ b/sample-app/src/main/java/com/sinch/sample/numbers/active/List.java @@ -5,7 +5,6 @@ import com.sinch.sdk.domains.numbers.models.requests.ActiveNumberListRequestParameters; import com.sinch.sdk.domains.numbers.models.responses.ActiveNumberListResponse; import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; public class List extends BaseApplication { @@ -36,12 +35,14 @@ public void run() { .setType(NumberType.LOCAL) .setPageSize(2) .build()); - AtomicInteger page = new AtomicInteger(1); - response - .iterator() - .forEachRemaining( - value -> - LOGGER.info( - String.format("Response (page %d): %s", page.getAndIncrement(), value))); + + LOGGER.info("Response"); + + response.stream() + .peek(a -> LOGGER.info(a.toString())) + .forEach( + a -> { + /* just here to consume the stream*/ + }); } } diff --git a/sample-app/src/main/java/com/sinch/sample/numbers/available/Rent.java b/sample-app/src/main/java/com/sinch/sample/numbers/available/Rent.java index a1205c49..449312fd 100644 --- a/sample-app/src/main/java/com/sinch/sample/numbers/available/Rent.java +++ b/sample-app/src/main/java/com/sinch/sample/numbers/available/Rent.java @@ -35,7 +35,7 @@ public void run() { RentVoiceConfigurationRequestParameters.builder() // .setAppId("app id") .build(); AvailableNumberRentRequestParameters parameters = - new AvailableNumberRentRequestParameters(rentSms, rentVoice, "foo callback"); + new AvailableNumberRentRequestParameters(null, null, "foo callback"); ActiveNumber value = client.numbers().available().rent(phoneNumber, parameters); LOGGER.info("Response :" + value); diff --git a/sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportById.java b/sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportById.java similarity index 89% rename from sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportById.java rename to sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportById.java index 1b46db4b..12f23ec4 100644 --- a/sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportById.java +++ b/sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportById.java @@ -11,15 +11,15 @@ import java.io.IOException; import java.util.logging.Logger; -public class GetReportById extends BaseApplication { +public class ReportById extends BaseApplication { - private static final Logger LOGGER = Logger.getLogger(GetReportById.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ReportById.class.getName()); - public GetReportById() throws IOException {} + public ReportById() throws IOException {} public static void main(String[] args) { try { - new GetReportById().run(); + new ReportById().run(); } catch (Exception e) { LOGGER.severe(e.getMessage()); e.printStackTrace(); diff --git a/sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportByIdentity.java b/sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportByIdentity.java similarity index 88% rename from sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportByIdentity.java rename to sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportByIdentity.java index 67db0fd7..c52f4e28 100644 --- a/sample-app/src/main/java/com/sinch/sample/verification/verifications/GetReportByIdentity.java +++ b/sample-app/src/main/java/com/sinch/sample/verification/verifications/ReportByIdentity.java @@ -12,15 +12,15 @@ import java.io.IOException; import java.util.logging.Logger; -public class GetReportByIdentity extends BaseApplication { +public class ReportByIdentity extends BaseApplication { - private static final Logger LOGGER = Logger.getLogger(GetReportByIdentity.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ReportByIdentity.class.getName()); - public GetReportByIdentity() throws IOException {} + public ReportByIdentity() throws IOException {} public static void main(String[] args) { try { - new GetReportByIdentity().run(); + new ReportByIdentity().run(); } catch (Exception e) { LOGGER.severe(e.getMessage()); e.printStackTrace(); diff --git a/sample-app/src/main/java/com/sinch/sample/verification/verifications/Start.java b/sample-app/src/main/java/com/sinch/sample/verification/verifications/Start.java index 6d09aef8..d48136b9 100644 --- a/sample-app/src/main/java/com/sinch/sample/verification/verifications/Start.java +++ b/sample-app/src/main/java/com/sinch/sample/verification/verifications/Start.java @@ -1,13 +1,10 @@ package com.sinch.sample.verification.verifications; import com.sinch.sample.BaseApplication; -import com.sinch.sdk.domains.verification.models.Identity; import com.sinch.sdk.domains.verification.models.NumberIdentity; import com.sinch.sdk.domains.verification.models.VerificationMethodType; -import com.sinch.sdk.domains.verification.models.VerificationReference; import com.sinch.sdk.domains.verification.models.requests.StartVerificationFlashCallRequestParameters; import com.sinch.sdk.domains.verification.models.requests.StartVerificationRequestParameters; -import com.sinch.sdk.domains.verification.models.response.StartVerificationResponse; import java.io.IOException; import java.util.logging.Logger; @@ -15,7 +12,8 @@ public class Start extends BaseApplication { private static final Logger LOGGER = Logger.getLogger(Start.class.getName()); - public Start() throws IOException {} + public Start() throws IOException { + } public static void main(String[] args) { try { @@ -30,13 +28,13 @@ public void run() { LOGGER.info("Start verification for : " + phoneNumber); - Identity identity = NumberIdentity.builder().setEndpoint(phoneNumber).build(); + var identity = NumberIdentity.builder().setEndpoint(phoneNumber).build(); - VerificationMethodType method = VerificationMethodType.SMS; + var method = VerificationMethodType.SMS; StartVerificationRequestParameters.Builder builder; - if (method != VerificationMethodType.FLASH_CALL) { + if (method != VerificationMethodType.CALLOUT) { builder = StartVerificationRequestParameters.builder(method); } else { // Dedicated flashcall builder usage do not require setting explicit verification method @@ -47,10 +45,11 @@ public void run() { .setDialTimeOut(17); } - builder.setIdentity(identity).setReference(VerificationReference.valueOf("a test reference")); + // process common properties + builder.setIdentity(identity); + + var response = client.verification().verifications().start(builder.build()); - StartVerificationResponse response = - client.verification().verifications().start(builder.build()); LOGGER.info("Response :" + response); } } diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java b/sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java new file mode 100644 index 00000000..494ebd41 --- /dev/null +++ b/sample-app/src/main/java/com/sinch/sample/webhooks/VerificationApplication.java @@ -0,0 +1,26 @@ +package com.sinch.sample.webhooks; + +import com.sinch.sample.Utils; +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.models.Configuration; +import java.util.Properties; +import java.util.logging.Logger; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class VerificationApplication { + private static final Logger LOGGER = Logger.getLogger(VerificationApplication.class.getName()); + + public static void main(String[] args) { + + SpringApplication.run(VerificationApplication.class, args); + } + + @Bean + public SinchClient sinchClient() { + Configuration configuration = Utils.loadConfiguration(LOGGER); + return new SinchClient(configuration); + } +} diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationController.java b/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationController.java new file mode 100644 index 00000000..1214e6bd --- /dev/null +++ b/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationController.java @@ -0,0 +1,77 @@ +package com.sinch.sample.webhooks.verification; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationRequestEvent; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResultEvent; +import java.util.Map; +import java.util.logging.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +@RestController +public class VerificationController { + + private final SinchClient sinchClient; + private final VerificationService service; + private static final Logger LOGGER = Logger.getLogger(VerificationController.class.getName()); + + @Autowired + public VerificationController(SinchClient sinchClient, VerificationService service) { + this.sinchClient = sinchClient; + this.service = service; + } + + @PostMapping( + value = "/VerificationEvent", + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + public String VerificationEvent( + @RequestHeader Map headers, @RequestBody String body) { + + LOGGER.finest("Received body:" + body); + LOGGER.finest("Received headers: " + headers); + + // ensure valid authentication to handle request + var validAuth = + sinchClient + .verification() + .webhooks() + .checkAuthentication( + // The HTTP verb this controller is managing + "POST", + // The URI this controller is managing + "/VerificationEvent", + // request headers + headers, + // request payload body + body); + + // token validation failed + if (!validAuth) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } + + // decode the request payload + var event = sinchClient.verification().webhooks().unserializeVerificationEvent(body); + + // let business layer process the request + var response = switch (event) { + case VerificationRequestEvent e -> service.verificationEvent(e); + case VerificationResultEvent e -> { + service.verificationEvent(e); + yield null; + } + default -> throw new IllegalStateException("Unexpected value: " + event); + }; + + LOGGER.finest("response: " + response); + + return response; + } +} diff --git a/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationService.java b/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationService.java new file mode 100644 index 00000000..771cacdb --- /dev/null +++ b/sample-app/src/main/java/com/sinch/sample/webhooks/verification/VerificationService.java @@ -0,0 +1,62 @@ +package com.sinch.sample.webhooks.verification; + +import com.sinch.sdk.SinchClient; +import com.sinch.sdk.core.exceptions.ApiException; +import com.sinch.sdk.domains.verification.models.VerificationMethodType; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationRequestEvent; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponse; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponseActionType; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponseCallout; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponseFlashCall; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResponseSMS; +import com.sinch.sdk.domains.verification.models.webhooks.VerificationResultEvent; +import java.util.logging.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class VerificationService { + + private final SinchClient sinchClient; + + private static final Logger LOGGER = Logger.getLogger(VerificationService.class.getName()); + + @Autowired + public VerificationService(SinchClient sinchClient) { + this.sinchClient = sinchClient; + } + + public String verificationEvent(VerificationRequestEvent event) { + + LOGGER.info("decoded event :" + event); + + VerificationResponse.Builder builder; + var method = event.getMethod(); + + if (method == VerificationMethodType.SMS) { + builder = VerificationResponseSMS.builder().setCode(1234); + } else if (method == VerificationMethodType.FLASH_CALL) { + builder = VerificationResponseFlashCall.builder().setDialTimeout(12); + } else if (method == VerificationMethodType.CALLOUT) { + builder = VerificationResponseCallout.builder().setCode(4567) + // only "en-US" is supported, not mandatory to set it + // .setLocale("en-US") + ; + } else { + throw new ApiException("Unexpected methode value: '" + method + "'"); + } + + builder.setAction(VerificationResponseActionType.ALLOW); + + var response = + sinchClient.verification().webhooks().serializeVerificationResponse(builder.build()); + LOGGER.info("Response :" + response); + + return response; + } + + public void verificationEvent(VerificationResultEvent event) { + + LOGGER.info("Handle event :" + event); + } +} diff --git a/test-resources/src/test/resources/domains/verification/v1/VerificationReportFlashCallResponseDto.json b/test-resources/src/test/resources/domains/verification/v1/VerificationReportFlashCallResponseDto.json index e3cc0f17..c1034f13 100644 --- a/test-resources/src/test/resources/domains/verification/v1/VerificationReportFlashCallResponseDto.json +++ b/test-resources/src/test/resources/domains/verification/v1/VerificationReportFlashCallResponseDto.json @@ -4,5 +4,5 @@ "status": "FAIL", "reason": "Fraud", "reference": "my reference", - "source": "my source" + "source": "manual" } diff --git a/test-resources/src/test/resources/domains/verification/v1/VerificationReportSMSResponseDto.json b/test-resources/src/test/resources/domains/verification/v1/VerificationReportSMSResponseDto.json index 05652a76..338f647c 100644 --- a/test-resources/src/test/resources/domains/verification/v1/VerificationReportSMSResponseDto.json +++ b/test-resources/src/test/resources/domains/verification/v1/VerificationReportSMSResponseDto.json @@ -4,7 +4,7 @@ "status": "FAIL", "reason": "Fraud", "reference": "my reference", - "source": "my source", + "source": "intercepted", "price": { "verificationPrice": { "currencyId": "verificationPrice currency id",