Skip to content

Commit

Permalink
Merge pull request #165 from forcedotcom/develop
Browse files Browse the repository at this point in the history
Merging develop to master for release
  • Loading branch information
vish689 authored May 31, 2024
2 parents 91596e7 + 9c9d087 commit c5ca7a4
Show file tree
Hide file tree
Showing 34 changed files with 1,123 additions and 742 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.queryService</groupId>
<artifactId>Salesforce-CDP-jdbc</artifactId>
<version>1.19.5</version>
<version>1.19.6</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.salesforce.cdp.queryservice.auth;

import com.salesforce.cdp.queryservice.auth.token.CoreToken;
import com.salesforce.cdp.queryservice.auth.token.OffcoreToken;
import com.salesforce.cdp.queryservice.util.Constants;
import com.salesforce.cdp.queryservice.util.HttpHelper;
import com.salesforce.cdp.queryservice.util.TokenException;
import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.*;

import static com.salesforce.cdp.queryservice.util.Messages.FAILED_LOGIN_2;
import static com.salesforce.cdp.queryservice.util.Messages.TOKEN_EXCHANGE_FAILURE;

@Slf4j
public class TokenExchangeHelper {

private final Properties properties;
private final OkHttpClient client;

public TokenExchangeHelper(Properties properties, OkHttpClient client) {
this.properties = properties;
this.client = client;
}

public OffcoreToken exchangeToken(CoreToken coreToken) throws TokenException {
return exchangeToken(properties.getProperty(Constants.LOGIN_URL),
coreToken.getAccessToken(), properties.getProperty(Constants.DATASPACE));
}

private OffcoreToken exchangeToken(String url, String coreToken, String dataspace) throws TokenException {
String token_url = url + Constants.TOKEN_EXCHANGE_URL;
Map<String, String> requestBody = new HashMap<>();
requestBody.put(Constants.GRANT_TYPE_NAME, Constants.GRANT_TYPE);
requestBody.put(Constants.SUBJECT_TOKEN_TYPE_NAME, Constants.SUBJECT_TOKEN_TYPE);
requestBody.put(Constants.SUBJECT_TOKEN, coreToken);
if(StringUtils.isNotBlank(dataspace)) {requestBody.put(Constants.DATASPACE,dataspace);}
Calendar expireTime = Calendar.getInstance();
Response response = null;
try {
response = login(requestBody, token_url);
OffcoreToken token = HttpHelper.handleSuccessResponse(response, OffcoreToken.class, false);
if (token.getErrorDescription() != null) {
log.error("Token exchange failed with error {}", token.getErrorDescription());
TokenUtils.invalidateCoreToken(url, coreToken, client);
String message = token.getErrorDescription();
throw new TokenException(message);
}
expireTime.add(Calendar.SECOND, token.getExpiresIn());
token.setExpireTime(expireTime);
return token;
} catch (IOException e) {
log.error("Caught exception while exchanging the offcore token", e);
TokenUtils.invalidateCoreToken(url, coreToken, client);
throw new TokenException(TOKEN_EXCHANGE_FAILURE, e);
}
}

private Response login(Map<String, String> requestBody, String url) throws TokenException {
FormBody.Builder formBody = new FormBody.Builder();
requestBody.entrySet().stream().filter(e -> e.getValue() != null).forEach(e -> formBody.addEncoded(e.getKey(), e.getValue()));
Map<String, String> headers = Collections.singletonMap(Constants.CONTENT_TYPE, Constants.URL_ENCODED_CONTENT);
log.info(requestBody.toString());
try {
Request request = HttpHelper.buildRequest(Constants.POST, url, formBody.build(), headers);
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
log.error("login failed with status code {}", response.code());
HttpHelper.handleErrorResponse(response, Constants.ERROR_DESCRIPTION);
}
return response;
} catch (IOException e) {
log.error("login failed", e);
throw new TokenException(FAILED_LOGIN_2, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.salesforce.cdp.queryservice.auth;

import com.salesforce.cdp.queryservice.auth.token.CoreToken;
import com.salesforce.cdp.queryservice.auth.token.OffcoreToken;
import com.salesforce.cdp.queryservice.util.TokenException;
import okhttp3.OkHttpClient;

import java.util.Properties;

public class TokenManager {

private final TokenProvider tokenProvider;

public TokenManager(Properties properties, OkHttpClient client) throws TokenException {
tokenProvider = TokenProviderFactory.getTokenProvider(properties, client);
}

public TokenManager(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}

public CoreToken getCoreToken() throws TokenException {
return tokenProvider.getCoreToken();
}

public OffcoreToken getOffcoreToken() throws TokenException {
return tokenProvider.getOffcoreToken();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.salesforce.cdp.queryservice.auth;

import com.salesforce.cdp.queryservice.auth.token.CoreToken;
import com.salesforce.cdp.queryservice.auth.token.OffcoreToken;
import com.salesforce.cdp.queryservice.util.TokenException;

public interface TokenProvider {
CoreToken getCoreToken() throws TokenException;
OffcoreToken getOffcoreToken() throws TokenException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.salesforce.cdp.queryservice.auth;

import com.salesforce.cdp.queryservice.auth.jwt.JwtTokenFlow;
import com.salesforce.cdp.queryservice.auth.jwt.JwtLoginClient;
import com.salesforce.cdp.queryservice.auth.refresh.RefreshTokenFlow;
import com.salesforce.cdp.queryservice.auth.refresh.RefreshTokenClient;
import com.salesforce.cdp.queryservice.auth.unpwd.UnPwdAuthClient;
import com.salesforce.cdp.queryservice.auth.unpwd.UnPwdTokenFlow;
import com.salesforce.cdp.queryservice.util.Constants;
import com.salesforce.cdp.queryservice.util.PropertyUtils;
import com.salesforce.cdp.queryservice.util.TokenException;
import okhttp3.OkHttpClient;

import java.util.Properties;

public class TokenProviderFactory {

public static TokenProvider getTokenProvider(Properties properties, OkHttpClient client) throws TokenException {
TokenExchangeHelper tokenExchangeHelper = new TokenExchangeHelper(properties, client);
if (canSupportUsernamePasswordProvider(properties)) {
return new UnPwdTokenFlow(properties, new UnPwdAuthClient(client), tokenExchangeHelper);
} else if (canSupportJwtProvider(properties)) {
return new JwtTokenFlow(properties, new JwtLoginClient(client), tokenExchangeHelper);
} else if (canSupportRefreshToken(properties)) {
return new RefreshTokenFlow(properties, new RefreshTokenClient(client),
tokenExchangeHelper);
}
throw new TokenException("Sufficient properties for deciding auth flow is not provided");
}

private static boolean canSupportUsernamePasswordProvider(Properties properties) {
return PropertyUtils.isPropertyNonEmpty(properties, Constants.USER_NAME)
&& PropertyUtils.isPropertyNonEmpty(properties, Constants.PD);
}

private static boolean canSupportJwtProvider(Properties properties) {
return PropertyUtils.isPropertyNonEmpty(properties, Constants.USER_NAME)
&& PropertyUtils.isPropertyNonEmpty(properties, Constants.PRIVATE_KEY);
}

private static boolean canSupportRefreshToken(Properties properties) {
return PropertyUtils.isPropertyNonEmpty(properties, Constants.REFRESHTOKEN);
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/salesforce/cdp/queryservice/auth/TokenUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.salesforce.cdp.queryservice.auth;

import com.salesforce.cdp.queryservice.auth.token.CoreToken;
import com.salesforce.cdp.queryservice.auth.token.OffcoreToken;
import com.salesforce.cdp.queryservice.auth.token.Token;
import com.salesforce.cdp.queryservice.util.Constants;
import com.salesforce.cdp.queryservice.util.HttpHelper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

@Slf4j
public class TokenUtils {

public static boolean isValid(OffcoreToken offcoreToken) {
if (offcoreToken != null) {
Calendar now = Calendar.getInstance();
return now.compareTo(offcoreToken.getExpireTime()) < 1;
}
return false;
}

public static void invalidateCoreToken(String url, String coreToken, OkHttpClient client) {
if (coreToken == null) {
return;
}
try {
log.info("Invalidating the core token");
FormBody formBody = new FormBody.Builder().addEncoded("token", coreToken).build();
Map<String, String> headers = Collections.singletonMap(Constants.CONTENT_TYPE, Constants.URL_ENCODED_CONTENT);
String tokenRevokeUrl = url + Constants.TOKEN_REVOKE_URL;
Request request = HttpHelper.buildRequest(Constants.POST, tokenRevokeUrl, formBody, headers);
// Response is not needed for this call.
client.newCall(request).execute();
} catch (Exception e) {
log.error("Revoking the core token failed", e);
}
}

public static Map<String, String> getTokenWithUrl(Token token) {
Map<String, String> tokenWithUrlMap = new HashMap<>();
tokenWithUrlMap.put(Constants.ACCESS_TOKEN, token.getTokenType() + StringUtils.SPACE + token.getAccessToken());
tokenWithUrlMap.put(Constants.TENANT_URL, token.getInstanceUrl());
return tokenWithUrlMap;
}

public static void fillArray(byte[] bytes, byte val) {
if (bytes != null) {
Arrays.fill(bytes, val);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.salesforce.cdp.queryservice.auth.jwt;

import com.google.common.primitives.Bytes;
import com.salesforce.cdp.queryservice.auth.token.CoreToken;
import com.salesforce.cdp.queryservice.util.Constants;
import com.salesforce.cdp.queryservice.util.HttpHelper;
import com.salesforce.cdp.queryservice.util.TokenException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;

import static com.salesforce.cdp.queryservice.auth.TokenUtils.fillArray;
import static com.salesforce.cdp.queryservice.util.Messages.FAILED_LOGIN;
import static com.salesforce.cdp.queryservice.util.Messages.JWT_CREATION_FAILURE;

@Slf4j
public class JwtLoginClient {

private final OkHttpClient client;


public JwtLoginClient(OkHttpClient client) {
this.client = client;
}

public CoreToken keyPairAuthLogin(
String grantType, String clientId,
String userName, String privateKey, String audience, String tokenUrl
) throws TokenException {
byte[] grantTypeSegment = (Constants.GRANT_TYPE_NAME + Constants.TOKEN_ASSIGNMENT + grantType)
.getBytes(StandardCharsets.UTF_8);
byte[] jwsSegment = (Constants.ASSERTION + Constants.TOKEN_ASSIGNMENT +
createJwt(clientId, userName, privateKey, audience)).getBytes(StandardCharsets.UTF_8);
byte[] separator = Constants.TOKEN_SEPARATOR.getBytes(StandardCharsets.UTF_8);

byte[] body = Bytes.concat(
grantTypeSegment, separator, jwsSegment
);
try {
RequestBody requestBody = RequestBody.create(body, MediaType.parse(Constants.URL_ENCODED_CONTENT));
Map<String, String> headers = Collections.singletonMap(Constants.CONTENT_TYPE, Constants.URL_ENCODED_CONTENT);
Request request = HttpHelper.buildRequest(Constants.POST, tokenUrl, requestBody, headers);
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
log.error("login with user credentials failed with status code {}", response.code());
HttpHelper.handleErrorResponse(response, Constants.ERROR_DESCRIPTION);
}
return HttpHelper.handleSuccessResponse(response, CoreToken.class, false);
} catch (IOException e) {
log.error("login with user credentials failed", e);
throw new TokenException(FAILED_LOGIN, e);
} finally {
fillArray(grantTypeSegment, (byte)0);
fillArray(jwsSegment, (byte)0);
fillArray(separator, (byte)0);
fillArray(body, (byte)0);
}
}



private static String createJwt(String clientId, String userName, String privateKey, String audience) throws TokenException {
Instant now = Instant.now();
String jwtToken = null;
try {
RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
jwtToken = Jwts.builder()
.setIssuer(clientId)
.setSubject(userName)
.setAudience(audience)
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plus(2l, ChronoUnit.MINUTES)))
.signWith(rsaPrivateKey, SignatureAlgorithm.RS256)
.compact();
} catch (Exception e) {
log.error("JWT assertion creation failed", e);
throw new TokenException(JWT_CREATION_FAILURE, e);
}

return jwtToken;
}

private static RSAPrivateKey getPrivateKey(String rsaPrivateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
rsaPrivateKey = rsaPrivateKey.replace(Constants.BEGIN_PRIVATE_KEY, "");
rsaPrivateKey = rsaPrivateKey.replace(Constants.END_PRIVATE_KEY, "");
rsaPrivateKey = rsaPrivateKey.replaceAll("\\s", "");

PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(rsaPrivateKey));
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPrivateKey privateKey = (RSAPrivateKey)kf.generatePrivate(keySpec);
return privateKey;
}
}
Loading

0 comments on commit c5ca7a4

Please sign in to comment.