From 6a53576739fd6b96d4cd32f466230fb8b4990234 Mon Sep 17 00:00:00 2001
From: Mevan <mevan.karu@gmail.com>
Date: Wed, 6 Nov 2024 17:26:12 +0530
Subject: [PATCH] Add Component ID to API key identifier

---
 .../enforcer/security/jwt/APIKeyAuthenticator.java  | 11 ++++++++---
 .../enforcer/security/jwt/APIKeyConstants.java      |  2 ++
 .../connect/enforcer/security/jwt/APIKeyUtils.java  |  8 ++++----
 .../security/jwt/APIKeyAuthenticatorTest.java       | 13 +++++++++++++
 4 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticator.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticator.java
index caf7f8ff89..c009838caf 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticator.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticator.java
@@ -109,7 +109,12 @@ protected String retrieveTokenFromRequestCtx(RequestContext requestContext) thro
                     APISecurityConstants.API_AUTH_INVALID_CREDENTIALS_MESSAGE);
         }
         String keyHash = APIKeyUtils.generateAPIKeyHash(apiKey);
-        Object cachedJWT = CacheProvider.getGatewayAPIKeyJWTCache().getIfPresent(keyHash);
+        String componentId = requestContext.getMatchedAPI().getChoreoComponentInfo().getComponentID();
+        if (componentId == null) {
+            componentId = "";
+        }
+        String apiKeyId = keyHash + APIKeyConstants.API_KEY_ID_SEPARATOR + componentId;
+        Object cachedJWT = CacheProvider.getGatewayAPIKeyJWTCache().getIfPresent(apiKeyId);
         if (cachedJWT != null && !APIKeyUtils.isJWTExpired((String) cachedJWT)) {
             if (log.isDebugEnabled()) {
                 log.debug("Token retrieved from the cache. Token: " + FilterUtils.getMaskedToken(keyHash));
@@ -117,14 +122,14 @@ protected String retrieveTokenFromRequestCtx(RequestContext requestContext) thro
             return (String) cachedJWT;
         }
         // Exchange the API Key to a JWT token.
-        Optional<String> jwt = APIKeyUtils.exchangeAPIKeyToJWT(keyHash);
+        Optional<String> jwt = APIKeyUtils.exchangeAPIKeyToJWT(apiKeyId);
         if (jwt.isEmpty()) {
             throw new APISecurityException(APIConstants.StatusCodes.UNAUTHENTICATED.getCode(),
                     APISecurityConstants.API_AUTH_INVALID_CREDENTIALS,
                     APISecurityConstants.API_AUTH_INVALID_CREDENTIALS_MESSAGE);
         }
         // Cache the JWT token.
-        CacheProvider.getGatewayAPIKeyJWTCache().put(keyHash, jwt.get());
+        CacheProvider.getGatewayAPIKeyJWTCache().put(apiKeyId, jwt.get());
         return jwt.get();
     }
 
diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyConstants.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyConstants.java
index 82fc8613ba..746213361c 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyConstants.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyConstants.java
@@ -28,6 +28,8 @@ public class APIKeyConstants {
 
     public static final String API_KEY_JSON_KEY = "key";
 
+    public static final String API_KEY_ID_SEPARATOR = "#";
+
     public static final String PAT_EXCHANGE_ENDPOINT = "/internal/pat";
     public static final String API_KEY_EXCHANGE_ENDPOINT = "/internal/apiKey/token";
 }
diff --git a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyUtils.java b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyUtils.java
index 19351df21a..6a929ce6f0 100644
--- a/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyUtils.java
+++ b/enforcer-parent/enforcer/src/main/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyUtils.java
@@ -142,12 +142,12 @@ public static Optional<String> exchangePATToJWT(String patHash) {
     }
 
     /**
-     * Exchange a given API key hash to a JWT token.
+     * Exchange a given API key ID to a JWT token.
      *
-     * @param apiKeyHash    API Key Hash
+     * @param apiKeyId    API Key Hash + "#" + Target component ID.
      * @return JWT corresponding to given API Key.
      */
-    public static Optional<String> exchangeAPIKeyToJWT(String apiKeyHash) {
+    public static Optional<String> exchangeAPIKeyToJWT(String apiKeyId) {
 
         URL url = null;
         try {
@@ -162,7 +162,7 @@ public static Optional<String> exchangeAPIKeyToJWT(String apiKeyHash) {
             // Create a request to exchange API key to JWT.
             HttpPost exchangeRequest = new HttpPost(url.toURI());
             exchangeRequest.addHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
-            exchangeRequest.setEntity(new StringEntity(createKeyHashExchangeRequest(apiKeyHash)));
+            exchangeRequest.setEntity(new StringEntity(createKeyHashExchangeRequest(apiKeyId)));
             try (CloseableHttpResponse response = httpClient.execute(exchangeRequest)) {
                 if (response.getStatusLine().getStatusCode() == 200) {
                     HttpEntity entity = response.getEntity();
diff --git a/enforcer-parent/enforcer/src/test/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticatorTest.java b/enforcer-parent/enforcer/src/test/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticatorTest.java
index 47371e7fd2..082f282a7b 100644
--- a/enforcer-parent/enforcer/src/test/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticatorTest.java
+++ b/enforcer-parent/enforcer/src/test/java/org/wso2/choreo/connect/enforcer/security/jwt/APIKeyAuthenticatorTest.java
@@ -30,6 +30,7 @@
 import org.wso2.carbon.apimgt.common.gateway.dto.JWTConfigurationDto;
 import org.wso2.choreo.connect.enforcer.common.CacheProvider;
 import org.wso2.choreo.connect.enforcer.commons.model.APIConfig;
+import org.wso2.choreo.connect.enforcer.commons.model.ChoreoComponentInfo;
 import org.wso2.choreo.connect.enforcer.commons.model.RequestContext;
 import org.wso2.choreo.connect.enforcer.config.ConfigHolder;
 import org.wso2.choreo.connect.enforcer.config.EnforcerConfig;
@@ -68,9 +69,13 @@ public void setup() {
     public void retrieveTokenFromRequestCtxTest_invalidKey() {
 
         RequestContext.Builder requestContextBuilder = new RequestContext.Builder("/api-key");
+        ChoreoComponentInfo choreoComponentInfo = new ChoreoComponentInfo();
+        choreoComponentInfo.setComponentID("component_id");
         requestContextBuilder.matchedAPI(new APIConfig.Builder("Petstore")
                 .basePath("/test")
+                .uuid("6003a3b7-af0f-4fb3-853e-a6562b2345f2")
                 .apiType("REST")
+                .choreoComponentInfo(choreoComponentInfo)
                 .build());
         Map<String, String> headersMap = new HashMap<>();
         headersMap.put("choreo-api-key",
@@ -97,9 +102,13 @@ public void retrieveTokenFromRequestCtxTest_cached_validKey() throws APISecurity
         PowerMockito.when(CacheProvider.getGatewayAPIKeyJWTCache()).thenReturn(gatewayAPIKeyJWTCache);
         PowerMockito.when(gatewayAPIKeyJWTCache.getIfPresent(Mockito.anyString())).thenReturn(mockJWT);
 
+        ChoreoComponentInfo choreoComponentInfo = new ChoreoComponentInfo();
+        choreoComponentInfo.setComponentID("component_id");
         RequestContext.Builder requestContextBuilder = new RequestContext.Builder("/api-key");
         requestContextBuilder.matchedAPI(new APIConfig.Builder("Petstore")
                 .basePath("/test")
+                .uuid("6003a3b7-af0f-4fb3-853e-a6562b2345f2")
+                .choreoComponentInfo(choreoComponentInfo)
                 .apiType("REST")
                 .build());
         Map<String, String> headersMap = new HashMap<>();
@@ -128,10 +137,14 @@ public void retrieveTokenFromRequestCtxTest_validKey() throws APISecurityExcepti
         PowerMockito.when(CacheProvider.getGatewayAPIKeyJWTCache()).thenReturn(gatewayAPIKeyJWTCache);
         PowerMockito.when(gatewayAPIKeyJWTCache.getIfPresent(Mockito.anyString())).thenReturn(null);
 
+        ChoreoComponentInfo choreoComponentInfo = new ChoreoComponentInfo();
+        choreoComponentInfo.setComponentID("component_id");
         RequestContext.Builder requestContextBuilder = new RequestContext.Builder("/api-key");
         requestContextBuilder.matchedAPI(new APIConfig.Builder("Petstore")
                 .basePath("/test")
+                .uuid("6003a3b7-af0f-4fb3-853e-a6562b2345f2")
                 .apiType("REST")
+                .choreoComponentInfo(choreoComponentInfo)
                 .build());
         Map<String, String> headersMap = new HashMap<>();
         headersMap.put("choreo-api-key",