Skip to content

Commit

Permalink
Merge pull request #2131 from SanojPunchihewa/oauth-cache
Browse files Browse the repository at this point in the history
Use endpoint URL and OAuth credentials when caching token
  • Loading branch information
SanojPunchihewa authored Feb 1, 2024
2 parents 7eaf56d + c06f4f7 commit 0650cc7
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,18 @@ private String decodeString(String value) {
}
}

private void processUrlTemplate(MessageContext synCtx) throws VariableExpansionException {
protected void processUrlTemplate(MessageContext synCtx) throws VariableExpansionException {

String evaluatedUri = resolveUrlTemplate(synCtx);
if (evaluatedUri != null) {
synCtx.setTo(new EndpointReference(evaluatedUri));
if (super.getDefinition() != null) {
synCtx.setProperty(EndpointDefinition.DYNAMIC_URL_VALUE, evaluatedUri);
}
}
}

protected String resolveUrlTemplate(MessageContext synCtx) throws VariableExpansionException {
Map<String, Object> variables = new HashMap<String, Object>();

/*The properties with uri.var.* are only considered for Outbound REST Endpoints*/
Expand All @@ -162,10 +173,10 @@ private void processUrlTemplate(MessageContext synCtx) throws VariableExpansionE
if (objProperty != null) {
if (objProperty instanceof String) {
variables.put(propertyKey.toString(),
decodeString((String) synCtx.getProperty(propertyKey.toString())));
decodeString((String) synCtx.getProperty(propertyKey.toString())));
} else {
variables.put(propertyKey.toString(),
decodeString(String.valueOf(synCtx.getProperty(propertyKey.toString()))));
decodeString(String.valueOf(synCtx.getProperty(propertyKey.toString()))));
}
}
}
Expand Down Expand Up @@ -224,14 +235,14 @@ private void processUrlTemplate(MessageContext synCtx) throws VariableExpansionE
(propertyKey.toString().startsWith(RESTConstants.REST_URI_VARIABLE_PREFIX)
|| propertyKey.toString().startsWith(RESTConstants.REST_QUERY_PARAM_PREFIX))) {
Object objProperty =
synCtx.getProperty(propertyKey.toString());
synCtx.getProperty(propertyKey.toString());
if (objProperty != null) {
if (objProperty instanceof String) {
variables.put(propertyKey.toString(),
(String) synCtx.getProperty(propertyKey.toString()));
(String) synCtx.getProperty(propertyKey.toString()));
} else {
variables.put(propertyKey.toString(),
(String) String.valueOf(synCtx.getProperty(propertyKey.toString())));
(String) String.valueOf(synCtx.getProperty(propertyKey.toString())));
}
}
}
Expand Down Expand Up @@ -282,14 +293,7 @@ private void processUrlTemplate(MessageContext synCtx) throws VariableExpansionE
}
}
}


if (evaluatedUri != null) {
synCtx.setTo(new EndpointReference(evaluatedUri));
if (super.getDefinition() != null) {
synCtx.setProperty(EndpointDefinition.DYNAMIC_URL_VALUE, evaluatedUri);
}
}
return evaluatedUri;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

package org.apache.synapse.endpoints;

import com.damnhandy.uri.template.VariableExpansionException;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.endpoints.auth.AuthHandler;
Expand Down Expand Up @@ -46,6 +48,7 @@ public OAuthConfiguredHTTPEndpoint(AuthHandler authHandler) {
public void send(MessageContext synCtx) {

try {
setResolvedUrlTemplate(synCtx);
oAuthHandler.setAuthHeader(synCtx);

// If this a blocking call, add 401 as a non error http status code
Expand Down Expand Up @@ -77,17 +80,22 @@ public void send(MessageContext synCtx) {
*/
public MessageContext retryCallWithNewToken(MessageContext synCtx) {
// remove the existing token from the cache so that a new token is generated
oAuthHandler.removeTokenFromCache();
// set RETRIED_ON_OAUTH_FAILURE property to true
synCtx.setProperty(AuthConstants.RETRIED_ON_OAUTH_FAILURE, true);
send(synCtx);
try {
// set RETRIED_ON_OAUTH_FAILURE property to true
synCtx.setProperty(AuthConstants.RETRIED_ON_OAUTH_FAILURE, true);
oAuthHandler.removeTokenFromCache(synCtx);
send(synCtx);
} catch (AuthException e) {
handleError(synCtx,
"Error removing access token for oauth configured http endpoint " + this.getName(), e);
}
return synCtx;
}

@Override
public void destroy() {

oAuthHandler.removeTokenFromCache();
oAuthHandler.removeTokensFromCache();
super.destroy();
}

Expand All @@ -109,4 +117,21 @@ private void handleError(MessageContext synCtx, String message, Exception except
log.error(errorMsg);
informFailure(synCtx, SynapseConstants.ENDPOINT_AUTH_FAILURE, errorMsg);
}

private void setResolvedUrlTemplate(MessageContext messageContext) {
String resolvedUrl = resolveUrlTemplate(messageContext);
if (resolvedUrl != null) {
messageContext.setTo(new EndpointReference(resolvedUrl));
if (super.getDefinition() != null) {
messageContext.setProperty(EndpointDefinition.DYNAMIC_URL_VALUE, resolvedUrl);
}
}
}

@Override
protected void processUrlTemplate(MessageContext synCtx) throws VariableExpansionException {
// Since we set the resolved URL in the OAuthConfiguredHTTPEndpoint.send method
// return the processUrlTemplate to skip re-resolving at HTTPEndpoint.
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.apache.synapse.endpoints.auth.AuthConstants;
import org.apache.synapse.endpoints.auth.AuthException;

import java.util.Objects;

/**
* This class is used to handle Authorization code grant oauth.
*/
Expand Down Expand Up @@ -74,6 +76,15 @@ protected OMElement serializeSpecificOAuthConfigs(OMFactory omFactory) {
return authCode;
}

@Override
protected int getHash(MessageContext messageContext) throws AuthException {
return Objects.hash(messageContext.getTo().getAddress(), OAuthUtils.resolveExpression(getTokenUrl(), messageContext),
OAuthUtils.resolveExpression(getClientId(), messageContext), OAuthUtils.resolveExpression(getClientSecret(),
messageContext), OAuthUtils.resolveExpression(getRefreshToken(), messageContext),
getRequestParametersAsString(messageContext), getResolvedCustomHeadersMap(getCustomHeadersMap(),
messageContext));
}

/**
* Return the refresh token secret relevant to the Authorization Code Handler.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.apache.synapse.endpoints.auth.AuthConstants;
import org.apache.synapse.endpoints.auth.AuthException;

import java.util.Objects;

/**
* This class is used to handle Client Credentials grant oauth.
*/
Expand Down Expand Up @@ -60,4 +62,12 @@ protected OMElement serializeSpecificOAuthConfigs(OMFactory omFactory) {

return omFactory.createOMElement(AuthConstants.CLIENT_CREDENTIALS, SynapseConstants.SYNAPSE_OMNAMESPACE);
}

@Override
protected int getHash(MessageContext messageContext) throws AuthException {
return Objects.hash(messageContext.getTo().getAddress(), OAuthUtils.resolveExpression(getTokenUrl(), messageContext),
OAuthUtils.resolveExpression(getClientId(), messageContext), OAuthUtils.resolveExpression(getClientSecret(),
messageContext), getRequestParametersAsString(messageContext),
getResolvedCustomHeadersMap(getCustomHeadersMap(), messageContext));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
public abstract class OAuthHandler implements AuthHandler {

private final String id;

private final String tokenApiUrl;
private final String clientId;
private final String clientSecret;
Expand Down Expand Up @@ -89,7 +88,7 @@ public void setAuthHeader(MessageContext messageContext) throws AuthException {
private String getToken(final MessageContext messageContext) throws AuthException {

try {
return TokenCache.getInstance().getToken(id, new Callable<String>() {
return TokenCache.getInstance().getToken(getId(messageContext), new Callable<String>() {
@Override
public String call() throws AuthException, IOException {
return OAuthClient.generateToken(OAuthUtils.resolveExpression(tokenApiUrl, messageContext),
Expand Down Expand Up @@ -129,12 +128,20 @@ public int compare(String o1, String o2) {
}
}

/**
* Method to remove the token from the cache when the token is invalid.
*/
public void removeTokenFromCache(MessageContext messageContext) throws AuthException {

TokenCache.getInstance().removeToken(getId(messageContext));
}

/**
* Method to remove the token from the cache when the endpoint is destroyed.
*/
public void removeTokenFromCache() {
public void removeTokensFromCache() {

TokenCache.getInstance().removeToken(id);
TokenCache.getInstance().removeTokens(id.concat("_"));
}

/**
Expand Down Expand Up @@ -313,7 +320,7 @@ public void setCustomHeaders(Map<String, String> customHeadersMap) {
* @param messageContext Message Context of the request which will be used to resolve dynamic expressions
* @return Map<String, String> Resolved custom headers
*/
private Map<String, String> getResolvedCustomHeadersMap(Map<String, String> customHeadersMap,
protected Map<String, String> getResolvedCustomHeadersMap(Map<String, String> customHeadersMap,
MessageContext messageContext) throws AuthException {

Map<String, String> resolvedCustomHeadersMap = null;
Expand All @@ -339,4 +346,9 @@ public int getSocketTimeout() {
return socketTimeout;
}

protected abstract int getHash(MessageContext messageContext) throws AuthException;

private String getId(MessageContext messageContext) throws AuthException {
return id.concat("_").concat(String.valueOf(getHash(messageContext)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.apache.synapse.endpoints.auth.AuthConstants;
import org.apache.synapse.endpoints.auth.AuthException;

import java.util.Objects;

/**
* This class is used to handle Password Credentials grant oauth.
*/
Expand Down Expand Up @@ -78,6 +80,16 @@ protected OMElement serializeSpecificOAuthConfigs(OMFactory omFactory) {
return passwordCredentials;
}

@Override
protected int getHash(MessageContext messageContext) throws AuthException {
return Objects.hash(messageContext.getTo().getAddress(), OAuthUtils.resolveExpression(getTokenUrl(), messageContext),
OAuthUtils.resolveExpression(getClientId(), messageContext), OAuthUtils.resolveExpression(getClientSecret(),
messageContext), OAuthUtils.resolveExpression(getUsername(), messageContext),
OAuthUtils.resolveExpression(getPassword(), messageContext),
getRequestParametersAsString(messageContext), getResolvedCustomHeadersMap(getCustomHeadersMap(),
messageContext));
}

public String getUsername() {

return username;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public String getToken(String id, Callable<String> callable) throws ExecutionExc
}

/**
* This method is called to remove the token from the cache when the endpoint is destroyed
* This method is called to remove the token from the cache when the token is invalid
*
* @param id id of the endpoint
*/
Expand All @@ -91,4 +91,12 @@ public void removeToken(String id) {
tokenMap.invalidate(id);
}

/**
* This method is called to remove the tokens from the cache when the endpoint is destroyed
*
* @param oauthHandlerId id of the OAuth handler bounded to the endpoint
*/
public void removeTokens(String oauthHandlerId) {
tokenMap.asMap().entrySet().removeIf(entry -> entry.getKey().startsWith(oauthHandlerId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,27 @@ public void testOAuthConfiguredEPWithDynamicValues() throws Exception {
HttpResponse response = client.doGet("http://127.0.0.1:8280/foodapi/list/dynamicValues");
assertEquals(HttpStatus.SC_OK, response.getStatus());
}

public void testOAuthConfiguredDynamicURLEP() throws Exception {

String payload1 = "<request>\n" +
"\t<ep_url>http://localhost:9000/foodservice/food</ep_url>\n" +
"\t<token_ep>http://localhost:9000/foodservice/token1</token_ep>\n" +
"</request>";

String payload2 = "<request>\n" +
"\t<ep_url>http://localhost:9000/foodservice/apple</ep_url>\n" +
"\t<token_ep>http://localhost:9000/foodservice/token2</token_ep>\n" +
"</request>";

BasicHttpClient client = new BasicHttpClient();
HttpResponse response = client.doPost("http://127.0.0.1:8280/foodapi/list/dynamicURL", payload1.getBytes(),
"text/xml");
assertEquals(HttpStatus.SC_OK, response.getStatus());

HttpResponse response2 = client.doPost("http://127.0.0.1:8280/foodapi/list/dynamicURL", payload2.getBytes(),
"text/xml");
assertEquals(HttpStatus.SC_OK, response2.getStatus());
assertEquals("1", response2.getBodyAsString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class Constants {

static final String refreshToken = "wxyz#9876";
static final String accessToken = "abcd@1234";
static final String accessToken2 = "jklm#6789";
static final String expiresIn = "3600";
static final String tokenType = "Bearer";
static final String clientId = "my_client_id";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class FoodService {

private int unauthorizedReqCount = 0;
private int tokenReqCount = 0;
private int appleServiceRequests = 0;

@POST
@Path("/token")
Expand All @@ -57,6 +58,38 @@ public Response getAccessToken(@Context HttpHeaders httpHeaders,
return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid Credentials").build();
}

@POST
@Path("/token1")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response getAccessToken1(@Context HttpHeaders httpHeaders,
MultivaluedMap<String, String> tokenRequestParams) {

String basicHeader = httpHeaders.getHeaderString("Authorization");

if (validateBasicAuthHeader(basicHeader)) {
return Response.status(Response.Status.OK).entity(new Token(Constants.accessToken, Constants.expiresIn,
Constants.tokenType)).build();
}
return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid Credentials").build();
}

@POST
@Path("/token2")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response getAccessToken2(@Context HttpHeaders httpHeaders,
MultivaluedMap<String, String> tokenRequestParams) {

String basicHeader = httpHeaders.getHeaderString("Authorization");

if (validateBasicAuthHeader(basicHeader)) {
return Response.status(Response.Status.OK).entity(new Token(Constants.accessToken2, Constants.expiresIn,
Constants.tokenType)).build();
}
return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid Credentials").build();
}

@POST
@Path("/custom-token")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
Expand Down Expand Up @@ -105,6 +138,22 @@ public Response getFoodItem(@Context HttpHeaders httpHeaders) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}

@GET
@Path("/apple")
@Produces(MediaType.APPLICATION_JSON)
public Response getFoodItem2(@Context HttpHeaders httpHeaders) {

appleServiceRequests++;
String authorizationHeader = httpHeaders.getHeaderString("Authorization");
if (authorizationHeader != null) {
String token = authorizationHeader.split(" ")[1];
if (token.equals(Constants.accessToken2)) {
return Response.status(Response.Status.OK).entity(appleServiceRequests).build();
}
}
return Response.status(Response.Status.UNAUTHORIZED).build();
}

@GET
@Path("/unauthorized")
@Produces(MediaType.APPLICATION_JSON)
Expand Down
Loading

0 comments on commit 0650cc7

Please sign in to comment.