diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/HTTPEndpoint.java b/modules/core/src/main/java/org/apache/synapse/endpoints/HTTPEndpoint.java index 6ff090c09c..ab5387f98b 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/HTTPEndpoint.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/HTTPEndpoint.java @@ -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 variables = new HashMap(); /*The properties with uri.var.* are only considered for Outbound REST Endpoints*/ @@ -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())))); } } } @@ -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()))); } } } @@ -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; } /** diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/OAuthConfiguredHTTPEndpoint.java b/modules/core/src/main/java/org/apache/synapse/endpoints/OAuthConfiguredHTTPEndpoint.java index adac33de58..2e218b2983 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/OAuthConfiguredHTTPEndpoint.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/OAuthConfiguredHTTPEndpoint.java @@ -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; @@ -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 @@ -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(); } @@ -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; + } } diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/AuthorizationCodeHandler.java b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/AuthorizationCodeHandler.java index 7d6c4a5800..dc3432e41a 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/AuthorizationCodeHandler.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/AuthorizationCodeHandler.java @@ -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. */ @@ -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. * diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/ClientCredentialsHandler.java b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/ClientCredentialsHandler.java index 2a1d21831e..df95cf1e81 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/ClientCredentialsHandler.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/ClientCredentialsHandler.java @@ -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. */ @@ -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)); + } } diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthHandler.java b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthHandler.java index 83ddf7ca21..b9d8f957da 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthHandler.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthHandler.java @@ -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; @@ -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() { + return TokenCache.getInstance().getToken(getId(messageContext), new Callable() { @Override public String call() throws AuthException, IOException { return OAuthClient.generateToken(OAuthUtils.resolveExpression(tokenApiUrl, messageContext), @@ -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("_")); } /** @@ -313,7 +320,7 @@ public void setCustomHeaders(Map customHeadersMap) { * @param messageContext Message Context of the request which will be used to resolve dynamic expressions * @return Map Resolved custom headers */ - private Map getResolvedCustomHeadersMap(Map customHeadersMap, + protected Map getResolvedCustomHeadersMap(Map customHeadersMap, MessageContext messageContext) throws AuthException { Map resolvedCustomHeadersMap = null; @@ -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))); + } } diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/PasswordCredentialsHandler.java b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/PasswordCredentialsHandler.java index 928fce7d5c..07f48e2916 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/PasswordCredentialsHandler.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/PasswordCredentialsHandler.java @@ -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. */ @@ -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; diff --git a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/TokenCache.java b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/TokenCache.java index 61fc973d60..6537c11db3 100644 --- a/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/TokenCache.java +++ b/modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/TokenCache.java @@ -82,7 +82,7 @@ public String getToken(String id, Callable 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 */ @@ -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)); + } } diff --git a/modules/integration/src/test/java/org/apache/synapse/samples/framework/tests/endpoint/Sample63.java b/modules/integration/src/test/java/org/apache/synapse/samples/framework/tests/endpoint/Sample63.java index caede6b321..5ef71a4029 100644 --- a/modules/integration/src/test/java/org/apache/synapse/samples/framework/tests/endpoint/Sample63.java +++ b/modules/integration/src/test/java/org/apache/synapse/samples/framework/tests/endpoint/Sample63.java @@ -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 = "\n" + + "\thttp://localhost:9000/foodservice/food\n" + + "\thttp://localhost:9000/foodservice/token1\n" + + ""; + + String payload2 = "\n" + + "\thttp://localhost:9000/foodservice/apple\n" + + "\thttp://localhost:9000/foodservice/token2\n" + + ""; + + 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()); + } } diff --git a/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/Constants.java b/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/Constants.java index cf2b5d7920..ca30a72085 100644 --- a/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/Constants.java +++ b/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/Constants.java @@ -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"; diff --git a/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/FoodService.java b/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/FoodService.java index 6a2826e6e6..e95167e85b 100644 --- a/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/FoodService.java +++ b/modules/samples/src/main/java/org/wso2/synapse/samples/jaxrs/foodsample/FoodService.java @@ -38,6 +38,7 @@ public class FoodService { private int unauthorizedReqCount = 0; private int tokenReqCount = 0; + private int appleServiceRequests = 0; @POST @Path("/token") @@ -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 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 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) @@ -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) diff --git a/repository/conf/sample/synapse_sample_63.xml b/repository/conf/sample/synapse_sample_63.xml index fbc676bfda..8e1d7d1e74 100644 --- a/repository/conf/sample/synapse_sample_63.xml +++ b/repository/conf/sample/synapse_sample_63.xml @@ -211,5 +211,33 @@ + + + + + + + + + + + my_client_id + my_client_secret + {$ctx:token_ep} + + + + + + + + + + + + + + +