Skip to content

Commit

Permalink
Merge pull request #29 from sinch/feat/verification
Browse files Browse the repository at this point in the history
Feature: verification API & webhooks
  • Loading branch information
JPPortier authored Dec 12, 2023
2 parents 30e7900 + a7f7325 commit 462c84e
Show file tree
Hide file tree
Showing 244 changed files with 20,275 additions and 403 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/samples-compilation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Samples Compilation

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- 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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ buildNumber.properties
# JDT-specific (Eclipse Java Development Tools)
.classpath
.idea
*.iml
*.iml

sample-app/src/main/resources/config.properties
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


</h1>
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion client/resources/config-default.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
oauth-url=https://auth.sinch.com/oauth2/token
numbers-server=https://numbers.api.sinch.com
sms-region=us
sms-server=https://zt.%s.sms.api.sinch.com
sms-server=https://zt.%s.sms.api.sinch.com
verification-server=https://verification.api.sinch.com
54 changes: 34 additions & 20 deletions client/src/main/com/sinch/sdk/SinchClient.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
package com.sinch.sdk;

import com.sinch.sdk.auth.AuthManager;
import com.sinch.sdk.auth.adapters.BasicAuthManager;
import com.sinch.sdk.auth.adapters.BearerAuthManager;
import com.sinch.sdk.core.http.HttpMapper;
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.domains.numbers.NumbersService;
import com.sinch.sdk.domains.sms.SMSService;
import com.sinch.sdk.domains.verification.VerificationService;
import com.sinch.sdk.http.HttpClientApache;
import com.sinch.sdk.models.Configuration;
import com.sinch.sdk.models.SMSRegion;
import java.io.IOException;
import java.io.InputStream;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.logging.Logger;
Expand All @@ -26,12 +21,13 @@
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";
private static final String SMS_REGION_KEY = "sms-region";
private static final String SMS_SERVER_KEY = "sms-server";
private static final String VERIFICATION_SERVER_KEY = "verification-server";

private static final String PROJECT_NAME_KEY = "project.name";
private static final String PROJECT_VERSION_KEY = "project.version";
Expand All @@ -47,8 +43,8 @@ public class SinchClient {
private final Properties versionProperties;

private NumbersService numbers;

private SMSService sms;
private VerificationService verification;

private HttpClientApache httpClient;

Expand All @@ -75,6 +71,9 @@ public SinchClient(Configuration configuration) {
if (null == configuration.getSmsRegion() && props.containsKey(SMS_REGION_KEY)) {
builder.setSmsRegion(SMSRegion.from(props.getProperty(SMS_REGION_KEY)));
}
if (null == configuration.getVerificationUrl() && props.containsKey(VERIFICATION_SERVER_KEY)) {
builder.setVerificationUrl(props.getProperty(VERIFICATION_SERVER_KEY));
}
Configuration newConfiguration = builder.build();
checkConfiguration(newConfiguration);
this.configuration = newConfiguration;
Expand Down Expand Up @@ -128,13 +127,29 @@ public SMSService sms() {
return sms;
}

/**
* Get verification domain service
*
* @return Return instance onto verification API service
* @see <a
* href="https://developers.sinch.com/docs/verification/api-reference//">https://developers.sinch.com/docs/verification/api-reference//</a>
* @since 1.0
*/
public VerificationService verification() {
if (null == verification) {
verification = verificationInit();
}
return verification;
}

private void checkConfiguration(Configuration configuration) throws NullPointerException {
Objects.requireNonNull(configuration.getKeyId(), "'keyId' cannot be null");
Objects.requireNonNull(configuration.getKeySecret(), "'keySecret' cannot be null");
Objects.requireNonNull(configuration.getProjectId(), "'projectId' cannot be null");
Objects.requireNonNull(configuration.getOAuthUrl(), "'oauthUrl' cannot be null");
Objects.requireNonNull(configuration.getNumbersUrl(), "'numbersUrl' cannot be null");
Objects.requireNonNull(configuration.getSmsUrl(), "'smsUrl' cannot be null");
Objects.requireNonNull(configuration.getVerificationUrl(), "'verificationUrl' cannot be null");
}

private NumbersService numbersInit() {
Expand All @@ -152,6 +167,15 @@ private SMSService smsInit() {
return new com.sinch.sdk.domains.sms.adapters.SMSService(getConfiguration(), getHttpClient());
}

private VerificationService verificationInit() {
LOGGER.fine(
"Activate verification API with server='"
+ getConfiguration().getVerificationServer().getUrl()
+ "'");
return new com.sinch.sdk.domains.verification.adapters.VerificationService(
getConfiguration(), getHttpClient());
}

private Properties handlePropertiesFile(String fileName) {

Properties prop = new Properties();
Expand All @@ -165,21 +189,10 @@ private Properties handlePropertiesFile(String fileName) {

private HttpClientApache getHttpClient() {
if (null == httpClient || httpClient.isClosed()) {
AuthManager basicAuthManager = new BasicAuthManager(configuration);
BearerAuthManager bearerAuthManager = new BearerAuthManager(configuration, new HttpMapper());

Map<String, AuthManager> authManagers =
Stream.of(basicAuthManager, bearerAuthManager)
.map(e -> new AbstractMap.SimpleEntry<>(e.getSchema(), e))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// TODO: by adding a setter, we could imagine having another HTTP client provided
// programmatically or use
// configuration file referencing another class by name
this.httpClient = new HttpClientApache(authManagers);

// Avoid multiple and costly http client creation and reuse it for authManager
bearerAuthManager.setHttpClient(this.httpClient);
this.httpClient = new HttpClientApache();

// set SDK User-Agent
String userAgent = formatSdkUserAgentHeader(versionProperties);
Expand Down Expand Up @@ -211,4 +224,5 @@ private String formatAuxiliaryFlag(String auxiliaryFlag) {
}
return String.join(",", values);
}

}
14 changes: 0 additions & 14 deletions client/src/main/com/sinch/sdk/auth/AuthManager.java

This file was deleted.

41 changes: 24 additions & 17 deletions client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
package com.sinch.sdk.auth.adapters;

import com.sinch.sdk.auth.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.utils.Pair;
import com.sinch.sdk.models.Configuration;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;

public class BasicAuthManager implements AuthManager {
public static final String BASIC_SCHEMA_KEYWORD = "BasicAuth";
private static final String BASIC_AUTH_KEYWORD = "Basic";
private final Configuration configuration;

private static final String AUTH_KEYWORD = "Basic";
private final String keyId;
private final String keySecret;

public BasicAuthManager(Configuration configuration) {
this.configuration = configuration;
this(configuration.getKeyId(), configuration.getKeySecret());
}

public String getSchema() {
return BASIC_SCHEMA_KEYWORD;
public BasicAuthManager(String keyId, String keySecret) {
this.keyId = keyId;
this.keySecret = keySecret;
}

@Override
public void setHttpClient(HttpClient httpClient) {
// no op
public String getSchema() {
return SCHEMA_KEYWORD_BASIC;
}

@Override
Expand All @@ -30,14 +33,18 @@ public void resetToken() {
}

@Override
public String getAuthorizationHeaderValue() {
String key = configuration.getKeyId() == null ? "" : configuration.getKeyId();
String secret = configuration.getKeySecret() == null ? "" : configuration.getKeySecret();
public Collection<Pair<String, String>> getAuthorizationHeaders(
String timestamp, String method, String httpContentType, String path, String body) {
String key = keyId == null ? "" : keyId;
String secret = keySecret == null ? "" : keySecret;

String raw = key + ":" + secret;

return BASIC_AUTH_KEYWORD
+ " "
+ Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.UTF_8));
return Collections.singletonList(
new Pair<>(
"Authorization",
AUTH_KEYWORD
+ " "
+ Base64.getEncoder().encodeToString(raw.getBytes(StandardCharsets.UTF_8))));
}
}
52 changes: 35 additions & 17 deletions client/src/main/com/sinch/sdk/auth/adapters/BearerAuthManager.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
package com.sinch.sdk.auth.adapters;

import com.fasterxml.jackson.core.type.TypeReference;
import com.sinch.sdk.auth.AuthManager;
import com.sinch.sdk.auth.models.BearerAuthResponse;
import com.sinch.sdk.core.exceptions.ApiAuthException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.core.http.HttpMapper;
import com.sinch.sdk.core.http.HttpMethod;
import com.sinch.sdk.core.http.HttpRequest;
import com.sinch.sdk.core.http.HttpResponse;
import com.sinch.sdk.core.models.ServerConfiguration;
import com.sinch.sdk.core.utils.Pair;
import com.sinch.sdk.models.Configuration;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class BearerAuthManager implements AuthManager {
public static final String BEARER_SCHEMA_KEYWORD = "BearerAuth";
public static final String BEARER_EXPIRED_KEYWORD = "expired";
public static final String BEARER_AUTHENTICATE_RESPONSE_HEADER_KEYWORD = "www-authenticate";
private static final Logger LOGGER = Logger.getLogger(BearerAuthManager.class.getName());
private static final String BEARER_AUTH_KEYWORD = "Bearer";
private static final String AUTH_KEYWORD = "Bearer";
private static final int maxRefreshAttempt = 5;
private final Configuration configuration;
private final ServerConfiguration oAuthServer;
private final HttpMapper mapper;
private HttpClient httpClient;
private final HttpClient httpClient;
private final Map<String, AuthManager> authManagers;
private String token;

public BearerAuthManager(Configuration configuration, HttpMapper mapper) {
this.configuration = configuration;
this.mapper = mapper;
public BearerAuthManager(Configuration configuration, HttpMapper mapper, HttpClient httpClient) {
this(configuration.getKeyId(), configuration.getKeySecret(), configuration, mapper, httpClient);
}

public String getSchema() {
return BEARER_SCHEMA_KEYWORD;
public BearerAuthManager(
String keyId,
String keySecret,
Configuration configuration,
HttpMapper mapper,
HttpClient httpClient) {
this.oAuthServer = configuration.getOAuthServer();
this.mapper = mapper;
this.httpClient = httpClient;

AuthManager basicAuthManager = new BasicAuthManager(keyId, keySecret);
authManagers =
Stream.of(new AbstractMap.SimpleEntry<>(SCHEMA_KEYWORD_BASIC, basicAuthManager))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

@Override
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
public String getSchema() {
return SCHEMA_KEYWORD_BEARER;
}

@Override
Expand All @@ -46,12 +63,13 @@ public void resetToken() {
}

@Override
public String getAuthorizationHeaderValue() {
public Collection<Pair<String, String>> getAuthorizationHeaders(
String timestamp, String method, String httpContentType, String path, String body) {

if (token == null) {
refreshToken();
}
return BEARER_AUTH_KEYWORD + " " + token;
return Collections.singletonList(new Pair<>("Authorization", AUTH_KEYWORD + " " + token));
}

private void refreshToken() {
Expand Down Expand Up @@ -80,9 +98,9 @@ private Optional<String> getNewToken() {
null,
null,
Collections.singletonList("application/x-www-form-urlencoded"),
Collections.singletonList(BasicAuthManager.BASIC_SCHEMA_KEYWORD));
Collections.singletonList(SCHEMA_KEYWORD_BASIC));
try {
HttpResponse httpResponse = httpClient.invokeAPI(configuration.getOAuthServer(), request);
HttpResponse httpResponse = httpClient.invokeAPI(oAuthServer, authManagers, request);
BearerAuthResponse authResponse =
mapper.deserialize(httpResponse, new TypeReference<BearerAuthResponse>() {});
return Optional.ofNullable(authResponse.getAccessToken());
Expand Down
Loading

0 comments on commit 462c84e

Please sign in to comment.