Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verification API: application auth manager + Start verification #15

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
32 changes: 31 additions & 1 deletion client/src/main/com/sinch/sdk/SinchClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;
Expand All @@ -18,13 +19,14 @@ public class SinchClient {
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 Logger LOGGER = Logger.getLogger(SinchClient.class.getName());

private final Configuration configuration;
private NumbersService numbers;

private SMSService sms;
private VerificationService verification;

private HttpClientApache httpClient;

Expand All @@ -51,6 +53,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 @@ -98,13 +103,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 @@ -122,6 +143,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 handleDefaultConfigurationFile() {

Properties prop = new Properties();
Expand Down
26 changes: 18 additions & 8 deletions client/src/main/com/sinch/sdk/auth/adapters/BasicAuthManager.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package com.sinch.sdk.auth.adapters;

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 {

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

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

public String getSchema() {
Expand All @@ -23,14 +29,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 method, String httpContentType, String path, String body) {
String key = keyId == null ? "" : keyId;
String secret = keySecret == null ? "" : keySecret;

String raw = key + ":" + secret;

return 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))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
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;
Expand All @@ -24,15 +27,14 @@ public class BearerAuthManager implements AuthManager {
private static final Logger LOGGER = Logger.getLogger(BearerAuthManager.class.getName());
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 final HttpClient httpClient;
private final Map<String, AuthManager> authManagers;

private String token;

public BearerAuthManager(Configuration configuration, HttpMapper mapper, HttpClient httpClient) {
this.configuration = configuration;
this.oAuthServer = configuration.getOAuthServer();
this.mapper = mapper;
this.httpClient = httpClient;

Expand All @@ -52,12 +54,13 @@ public void resetToken() {
}

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

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

private void refreshToken() {
Expand Down Expand Up @@ -88,8 +91,7 @@ private Optional<String> getNewToken() {
Collections.singletonList("application/x-www-form-urlencoded"),
Collections.singletonList(SCHEMA_KEYWORD_BASIC));
try {
HttpResponse httpResponse =
httpClient.invokeAPI(configuration.getOAuthServer(), authManagers, request);
HttpResponse httpResponse = httpClient.invokeAPI(oAuthServer, authManagers, request);
BearerAuthResponse authResponse =
mapper.deserialize(httpResponse, new TypeReference<BearerAuthResponse>() {});
return Optional.ofNullable(authResponse.getAccessToken());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.sinch.sdk.auth.adapters;

import com.sinch.sdk.core.exceptions.ApiAuthException;
import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.utils.Pair;
import com.sinch.sdk.core.utils.StringUtil;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
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;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class VerificationApplicationAuthManager implements AuthManager {

private static final String AUTH_KEYWORD = "Application";
private static final String XTIMESTAMP_HEADER = "x-timestamp";
private final String key;
private final byte[] secret;

public VerificationApplicationAuthManager(String key, String base64Secret) {
this.key = key;
this.secret = Base64.getDecoder().decode(base64Secret);
}

// FIXME: Verification OAS file claim it support "Basic" but miss the "Application" definition
public String getSchema() {
return "Basic";
}

@Override
public void resetToken() {
// no op
}

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

String decodePath;
try {
decodePath = new URI(path).getPath();
} catch (URISyntaxException e) {
throw new ApiException(e);
}
// 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);
String key = this.key == null ? "" : this.key;

return Arrays.asList(
new Pair<>("Authorization", AUTH_KEYWORD + " " + key + ":" + encoded),
new Pair<>(XTIMESTAMP_HEADER, timestamp.toString()));
}

private String getBodyMD5Hash(String body) {
if (StringUtil.isEmpty(body)) {
return "";
}
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(body.getBytes(StandardCharsets.UTF_8));
byte[] encodedMd5ToBase64VerificationBody = Base64.getEncoder().encode(digest);
return new String(encodedMd5ToBase64VerificationBody, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException e) {
throw new ApiAuthException(e);
}
}

private String getSignature(
String method, String bodyMD5Hash, String httpContentType, Instant timestamp, String path) {
return String.join(
"\n",
method,
bodyMD5Hash,
null != httpContentType ? httpContentType : "",
XTIMESTAMP_HEADER + ":" + timestamp.toString(),
path);
}

private String encode(String stringToSign) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKeySpec);
byte[] hmacSha256 = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(hmacSha256), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new ApiAuthException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.sinch.sdk.domains.numbers.models;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;

/**
* Callback configuration
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.sinch.sdk.domains.numbers.models;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;

/**
* An object giving details on currency code and the amount charged.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.sinch.sdk.domains.numbers.models;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;

/**
* An object enabling to identify number by pattern
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import java.util.Collection;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import com.sinch.sdk.domains.numbers.models.Capability;
import com.sinch.sdk.domains.numbers.models.NumberPattern;
import com.sinch.sdk.domains.numbers.models.NumberType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import java.util.Optional;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import java.util.Optional;

/***
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;

/***
* SMS configuration parameters request to update an active number for a project
* @since 1.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import com.sinch.sdk.domains.numbers.models.Capability;
import com.sinch.sdk.domains.numbers.models.NumberPattern;
import com.sinch.sdk.domains.numbers.models.NumberType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import com.sinch.sdk.domains.numbers.models.Capability;
import com.sinch.sdk.domains.numbers.models.NumberPattern;
import com.sinch.sdk.domains.numbers.models.NumberType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import java.util.Optional;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import com.sinch.sdk.domains.numbers.models.NumberType;
import java.util.Collection;
import java.util.Optional;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;

/**
* Parameters request to update callback configuration
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sinch.sdk.domains.numbers.models.requests;

import com.sinch.sdk.domains.numbers.models.ActiveNumber.Builder;
import java.util.Optional;

/**
Expand Down
Loading
Loading