From 6bc3b273b387dcc987d170d0e95891c7258bd728 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Fri, 7 Nov 2025 11:33:07 +0100 Subject: [PATCH] map keys --- .../cz/trask/migration/AbstractProcess.java | 36 ++- .../impl/v45/ExportAppsToWso2FromV32.java | 222 +++++++++++------- .../model/v32/ApplicationDetail.java | 11 +- .../v45/ApplicationKeyMappingRequest45.java | 25 ++ .../v45/ApplicationKeyMappingResponse45.java | 73 ++++++ 5 files changed, 275 insertions(+), 92 deletions(-) create mode 100644 src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingRequest45.java create mode 100644 src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingResponse45.java diff --git a/src/main/java/cz/trask/migration/AbstractProcess.java b/src/main/java/cz/trask/migration/AbstractProcess.java index 4bd17a6..79ef974 100644 --- a/src/main/java/cz/trask/migration/AbstractProcess.java +++ b/src/main/java/cz/trask/migration/AbstractProcess.java @@ -136,14 +136,13 @@ public abstract class AbstractProcess { TokenResponse token = getToken(endpoints.getPublisherTokenUrl(), endpoints.getWso2User(), register, - "apim:api_view apim:api_create apim:api_manage apim:api_delete apim:api_publish " - + "apim:subscription_view apim:subscription_block apim:subscription_manage apim:external_services_discover " - + "apim:threat_protection_policy_create apim:threat_protection_policy_manage apim:document_create apim:document_manage " - + "apim:mediation_policy_view apim:mediation_policy_create apim:mediation_policy_manage apim:client_certificates_view " - + "apim:client_certificates_add apim:client_certificates_update apim:ep_certificates_view apim:ep_certificates_add " - + "apim:ep_certificates_update apim:publisher_settings apim:pub_alert_manage apim:shared_scope_manage apim:app_import_export " - + "apim:api_import_export apim:api_product_import_export apim:api_generate_key apim:common_operation_policy_view " - + "apim:common_operation_policy_manage apim:comment_write apim:comment_view apim:admin"); + "apim:api_view apim:api_create apim:api_manage apim:api_delete apim:api_publish apim:subscription_view apim:subscription_block "+ + "apim:subscription_manage apim:external_services_discover apim:threat_protection_policy_create apim:threat_protection_policy_manage "+ + "apim:document_create apim:document_manage apim:mediation_policy_view apim:mediation_policy_create apim:mediation_policy_manage "+ + "apim:client_certificates_view apim:client_certificates_add apim:client_certificates_update apim:ep_certificates_view apim:ep_certificates_add "+ + "apim:ep_certificates_update apim:publisher_settings apim:pub_alert_manage apim:shared_scope_manage apim:app_import_export apim:api_import_export "+ + "apim:api_product_import_export apim:api_generate_key apim:common_operation_policy_view apim:common_operation_policy_manage apim:comment_write "+ + "apim:comment_view apim:admin apim:subscribe apim:api_key apim:app_manage apim:sub_manage apim:store_settings apim:sub_alert_manage"); log.debug("Access token received – {}", token.getAccess_token()); @@ -500,4 +499,25 @@ public abstract class AbstractProcess { rule.setType(type); client.createArtifactRule(meta.getGroupId(), meta.getId(), rule); } + + protected Map createBearerAuthHeaders(TokenResponse tokenResponse) { + Map httpHeaders = new HashMap<>(); + httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token())); + return httpHeaders; + } + + protected Map createBasicAuthHeaders(String wso2User) throws Exception { + Map httpHeaders = new HashMap<>(); + + byte[] decoded = Base64.getDecoder().decode(wso2User); + String decodedstring = new String(decoded); + String[] decodedstringparts = decodedstring.split(":"); + + String username = decodedstringparts[0]; + String password = decodedstringparts[1]; + + httpHeaders.put("Authorization", "Basic ".concat(Base64.getEncoder() + .encodeToString(username.concat(":").concat(password).getBytes()))); + return httpHeaders; + } } diff --git a/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java b/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java index 99e4c73..a142e20 100644 --- a/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java +++ b/src/main/java/cz/trask/migration/impl/v45/ExportAppsToWso2FromV32.java @@ -1,9 +1,9 @@ package cz.trask.migration.impl.v45; -import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -13,8 +13,10 @@ import cz.trask.migration.AbstractProcess; import cz.trask.migration.model.HttpResponse; import cz.trask.migration.model.TokenResponse; import cz.trask.migration.model.v32.ApplicationDetail; +import cz.trask.migration.model.v32.ApplicationDetail.KeyManagerApp; import cz.trask.migration.model.v45.ApplicationCreateRequest; import cz.trask.migration.model.v45.ApplicationCreateResponse; +import cz.trask.migration.model.v45.ApplicationKeyMappingRequest45; import cz.trask.migration.model.v45.ApplicationListResponse45; import io.apicurio.registry.rest.v2.beans.ArtifactMetaData; import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults; @@ -26,11 +28,15 @@ import lombok.extern.log4j.Log4j2; @Log4j2 public class ExportAppsToWso2FromV32 extends AbstractProcess { + private static final int DEFAULT_TIMEOUT_MINUTES = 10; + private static final String APPLICATIONS_ENDPOINT_PATH = "/applications"; + private static final String CHANGE_OWNER_PATH = "/change-owner"; + private final AtomicInteger appCounter = new AtomicInteger(1); private SearchedArtifact adminsDefaultApplication; /** - * Main entry point for the import process. + * Main entry point for the export process. * * @throws RuntimeException if any error occurs */ @@ -45,24 +51,11 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { log.info("Found {} Apps", apps.getCount()); - int maxThreads = config.getMaxThreads(); - ExecutorService executor = Executors.newFixedThreadPool(maxThreads); - - for (SearchedArtifact app : apps.getArtifacts()) { - final int index = appCounter.getAndIncrement(); - executor.submit(() -> processApp(app, token, index, apps.getCount(), false)); - } - - executor.shutdown(); - if (!executor.awaitTermination(10, TimeUnit.MINUTES)) { - log.warn("Timeout waiting for App import tasks to finish"); - } - log.info("Finished processing Apps."); + processApplications(apps, token); if (adminsDefaultApplication != null) { - log.info("Found default application for admins: {}", adminsDefaultApplication.getName()); - processApp(adminsDefaultApplication, token, - appCounter.getAndIncrement(), apps.getCount(), true); + log.info("Found default application for admin: {}", adminsDefaultApplication.getName()); + processApp(adminsDefaultApplication, token, 1, 1, true); } } catch (Exception e) { log.error("Error while exporting Apps.", e); @@ -70,6 +63,22 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { } } + private void processApplications(ArtifactSearchResults apps, TokenResponse token) throws InterruptedException { + int maxThreads = config.getMaxThreads(); + ExecutorService executor = Executors.newFixedThreadPool(maxThreads); + + for (SearchedArtifact app : apps.getArtifacts()) { + final int index = appCounter.getAndIncrement(); + executor.submit(() -> processApp(app, token, index, apps.getCount(), false)); + } + + executor.shutdown(); + if (!executor.awaitTermination(DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES)) { + log.warn("Timeout waiting for App export tasks to finish"); + } + log.info("Finished processing Apps."); + } + private void processApp(SearchedArtifact app, TokenResponse tokenResponse, int index, int total, boolean createAdminApp) { long start = System.currentTimeMillis(); @@ -81,48 +90,7 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { app.getId(), null, null); for (SearchedVersion ver : versions.getVersions()) { - log.info(" - Found version: {}", ver.getVersion()); - - ArtifactMetaData amd = client.getArtifactMetaData(app.getGroupId(), app.getId()); - - byte[] content = client.getContentByGlobalId(amd.getGlobalId()).readAllBytes(); - - if (content != null && content.length > 0) { - ApplicationDetail appDetail = mapper - .readValue(content, ApplicationDetail.class); - - if (DEFAULT_APPLICATION_NAME.equals(appDetail.getName()) - && ADMIN_USERNAME.equals(appDetail.getOwner()) && !createAdminApp) { - adminsDefaultApplication = app; - deleteWso2ApplicationIfExists(appDetail, tokenResponse); - log.info(" - Skipping import of admins-default-application for now."); - continue; - } - - ApplicationCreateRequest appCreateRequest = mapAppDetailToCreateRequest(appDetail); - byte[] data = mapper.writeValueAsBytes(appCreateRequest); - log.info(" - Application {} with owner {} prepared for WSO2 import", appDetail.getName(), - appDetail.getOwner()); - - deleteWso2ApplicationIfExists(appDetail, tokenResponse); - - HttpResponse response = publishAppToWso2(appDetail.getName(), data, tokenResponse); - - if (response.getResponseCode() == 200 || response.getResponseCode() == 201) { - log.info(" - Application {} imported successfully", appDetail.getName()); - - ApplicationCreateResponse createdApp = mapper.readValue(response.getResponse(), - ApplicationCreateResponse.class); - log.info(" - Created Application ID in WSO2: {}", createdApp.getApplicationId()); - - if (!createdApp.getOwner().equals(appDetail.getOwner())) { - log.info(" - Changing owner of Application {} to {}", createdApp.getApplicationId(), - appDetail.getOwner()); - changeApplicationOwner(createdApp, appDetail.getOwner(), tokenResponse); - } - - } - } + processAppVersion(app, tokenResponse, ver, createAdminApp); } long end = System.currentTimeMillis(); @@ -132,8 +100,111 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { } } - private void deleteWso2ApplicationIfExists(ApplicationDetail appDetail, TokenResponse tokenResponse) { + private void processAppVersion(SearchedArtifact app, TokenResponse tokenResponse, SearchedVersion version, + boolean createAdminApp) { + try { + log.info(" - Found version: {}", version.getVersion()); + ArtifactMetaData amd = client.getArtifactMetaData(app.getGroupId(), app.getId()); + byte[] content = client.getContentByGlobalId(amd.getGlobalId()).readAllBytes(); + + if (content != null && content.length > 0) { + ApplicationDetail appDetail = mapper.readValue(content, ApplicationDetail.class); + + // Skip default admin application unless explicitly requested + if (DEFAULT_APPLICATION_NAME.equals(appDetail.getName()) + && ADMIN_USERNAME.equals(appDetail.getOwner()) && !createAdminApp) { + adminsDefaultApplication = app; + deleteWso2ApplicationIfExists(appDetail, tokenResponse); + log.info(" - Skipping import of admins-default-application for now."); + return; + } + + ApplicationCreateRequest appCreateRequest = mapAppDetailToCreateRequest(appDetail); + byte[] data = mapper.writeValueAsBytes(appCreateRequest); + log.info(" - Application {} with owner {} prepared for WSO2 export", appDetail.getName(), + appDetail.getOwner()); + + deleteWso2ApplicationIfExists(appDetail, tokenResponse); + + HttpResponse response = publishAppToWso2(appDetail.getName(), data, tokenResponse); + + if (response.getResponseCode() == 200 || response.getResponseCode() == 201) { + log.info(" - Application {} imported successfully", appDetail.getName()); + + ApplicationCreateResponse createdApp = mapper.readValue(response.getResponse(), + ApplicationCreateResponse.class); + log.info(" - Created Application ID in WSO2: {}", createdApp.getApplicationId()); + + if (appDetail.getKeyManagerWiseOAuthApp() != null + && !appDetail.getKeyManagerWiseOAuthApp().isEmpty()) { + log.info(" - Application {} has {} Key Mappings to create", + appDetail.getName(), appDetail.getKeyManagerWiseOAuthApp().size()); + for (Entry> entry : appDetail + .getKeyManagerWiseOAuthApp().entrySet()) { + String keyType = entry.getKey(); + + Map keyManagerApp = entry.getValue(); + + for (Entry kmEntry : keyManagerApp.entrySet()) { + String keyManager = kmEntry.getKey(); + KeyManagerApp oauthInfo = kmEntry.getValue(); + + log.info(" - Creating Key Mapping for Key Manager: {}", keyManager); + // Map to v4.5 request + ApplicationKeyMappingRequest45 keyMappingRequest = ApplicationKeyMappingRequest45 + .builder() + .consumerKey(oauthInfo.getClientId()) + .consumerSecret(oauthInfo.getClientSecret()) + .keyManager(keyManager) + .keyType(ApplicationKeyMappingRequest45.KeyType + .valueOf(keyType)) + .build(); + + byte[] kmData = mapper.writeValueAsBytes(keyMappingRequest); + publishApplicationKeyMappingToWso2(createdApp.getApplicationId(), kmData, + tokenResponse); + } + } + } + + if (!createdApp.getOwner().equals(appDetail.getOwner())) { + log.info(" - Changing owner of Application {} to {}", createdApp.getApplicationId(), + appDetail.getOwner()); + changeApplicationOwner(createdApp, appDetail.getOwner(), tokenResponse); + } + + } + } + } catch (Exception e) { + log.error("Error processing application version: {}", e.getMessage(), e); + } + } + + private void publishApplicationKeyMappingToWso2(String applicationId, byte[] kmData, TokenResponse tokenResponse) { + try { + // Publish the application key mapping data to WSO2 + Map httpHeaders = createBearerAuthHeaders(tokenResponse); + httpHeaders.put("Content-Type", "application/json"); + + String endpoint = config.getTarget().getDevPortalApiUrl() + "/v3/applications/" + applicationId + + "/map-keys"; + + HttpResponse response = makeDataRequest(endpoint, httpHeaders, kmData); + + if (response.getResponseCode() == 200 || response.getResponseCode() == 201) { + log.info(" - Key Mapping for Application {} created successfully in WSO2", applicationId); + } else { + log.warn(" - Key Mapping for Application {} creation in WSO2 failed with response code {}", + applicationId, response.getResponseCode()); + } + } catch (Exception e) { + log.error("IO error while creating Key Mapping for Application {}: {}", applicationId, + e.getMessage(), e); + } + } + + private void deleteWso2ApplicationIfExists(ApplicationDetail appDetail, TokenResponse tokenResponse) { // Resolve application id by name first (WSO2 Admin API works with applicationId // in paths) String resolvedAppId = getExistingApplicationId(config.getTarget().getAdminApiUrl(), appDetail, tokenResponse); @@ -143,11 +214,10 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { return; } - String endpoint = config.getTarget().getAdminApiUrl() + "/applications/" + resolvedAppId; + String endpoint = config.getTarget().getAdminApiUrl() + APPLICATIONS_ENDPOINT_PATH + "/" + resolvedAppId; try { - Map httpHeaders = new HashMap<>(); - httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token())); + Map httpHeaders = createBearerAuthHeaders(tokenResponse); HttpResponse response = makeRequest("DELETE", endpoint, httpHeaders, null); @@ -167,13 +237,11 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { private String getExistingApplicationId(String adminApiUrl, ApplicationDetail appDetail, TokenResponse tokenResponse) { try { - String url = adminApiUrl.concat(String.format("/applications")); + String url = adminApiUrl.concat(APPLICATIONS_ENDPOINT_PATH); - Map httpHeaders = new HashMap<>(); + Map httpHeaders = createBearerAuthHeaders(tokenResponse); Map params = new HashMap<>(); - httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token())); - HttpResponse response = makeRequest("GET", url, httpHeaders, params); if (response.getResponseCode() != 200) { @@ -200,16 +268,7 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { private HttpResponse publishAppToWso2(String name, byte[] data, TokenResponse tokenResponse) throws Exception { // Publish the application data to WSO2 - byte[] decoded = Base64.getDecoder().decode(config.getTarget().getWso2User()); - String decodedstring = new String(decoded); - String[] decodedstringparts = decodedstring.split(":"); - - String username = decodedstringparts[0]; - String password = decodedstringparts[1]; - Map httpHeaders = new HashMap<>(); - - httpHeaders.put("Authorization", "Basic ".concat(Base64.getEncoder() - .encodeToString(username.concat(":").concat(password).getBytes()))); + Map httpHeaders = createBasicAuthHeaders(config.getTarget().getWso2User()); httpHeaders.put("Content-Type", "application/json"); String endpoint = config.getTarget().getDevPortalApiUrl() + "/v3/applications"; @@ -219,12 +278,11 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess { private void changeApplicationOwner(ApplicationCreateResponse createdApp, String origOwner, TokenResponse tokenResponse) { - String endpoint = config.getTarget().getAdminApiUrl() + "/applications/" + createdApp.getApplicationId() - + "/change-owner?owner=" + origOwner; + String endpoint = config.getTarget().getAdminApiUrl() + APPLICATIONS_ENDPOINT_PATH + "/" + + createdApp.getApplicationId() + CHANGE_OWNER_PATH + "?owner=" + origOwner; try { - Map httpHeaders = new HashMap<>(); - httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token())); + Map httpHeaders = createBearerAuthHeaders(tokenResponse); HttpResponse response = makeRequest("POST", endpoint, httpHeaders, null); diff --git a/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java b/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java index 2d9bdb6..8884e6a 100644 --- a/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java +++ b/src/main/java/cz/trask/migration/model/v32/ApplicationDetail.java @@ -3,6 +3,8 @@ package cz.trask.migration.model.v32; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; + +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -97,8 +99,13 @@ public class ApplicationDetail { private String clientName; private String callBackURL; private String clientSecret; - private Map parameters; + private Map parameters = new HashMap(); private boolean isSaasApplication; - private Map appAttributes; + private String appOwner; + private String jsonString; + private Map appAttributes = new HashMap<>(); + private String jsonAppAttribute; + private String applicationUUID; + private String tokenType; } } diff --git a/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingRequest45.java b/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingRequest45.java new file mode 100644 index 0000000..9384c2a --- /dev/null +++ b/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingRequest45.java @@ -0,0 +1,25 @@ +package cz.trask.migration.model.v45; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ApplicationKeyMappingRequest45 { + @JsonProperty("consumerKey") + private String consumerKey; + + @JsonProperty("consumerSecret") + private String consumerSecret; + + @JsonProperty("keyManager") + private String keyManager; + + @JsonProperty("keyType") + private KeyType keyType; + + public enum KeyType { + PRODUCTION, SANDBOX + } +} diff --git a/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingResponse45.java b/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingResponse45.java new file mode 100644 index 0000000..420e9c7 --- /dev/null +++ b/src/main/java/cz/trask/migration/model/v45/ApplicationKeyMappingResponse45.java @@ -0,0 +1,73 @@ +package cz.trask.migration.model.v45; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApplicationKeyMappingResponse45 { + @JsonProperty("keyMappingId") + private String keyMappingId; + + @JsonProperty("keyManager") + private String keyManager; + + @JsonProperty("consumerKey") + private String consumerKey; + + @JsonProperty("consumerSecret") + private String consumerSecret; + + @JsonProperty("supportedGrantTypes") + private List supportedGrantTypes; + + @JsonProperty("callbackUrl") + private String callbackUrl; + + @JsonProperty("keyState") + private String keyState; + + @JsonProperty("keyType") + private KeyType keyType; + + @JsonProperty("mode") + private Mode mode; + + @JsonProperty("groupId") + private String groupId; + + @JsonProperty("token") + private Token token; + + @JsonProperty("additionalProperties") + private Map additionalProperties; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Token { + @JsonProperty("accessToken") + private String accessToken; + + @JsonProperty("tokenScopes") + private List tokenScopes; + + @JsonProperty("validityTime") + private Long validityTime; + } + + enum Mode { + MAPPED, CREATED + } + + enum KeyType { + PRODUCTION, SANDBOX + } +} \ No newline at end of file