This commit is contained in:
Radek Davidek 2025-11-07 11:33:07 +01:00
parent 7c6a8aa50b
commit 6bc3b273b3
5 changed files with 275 additions and 92 deletions

View File

@ -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<String, String> createBearerAuthHeaders(TokenResponse tokenResponse) {
Map<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token()));
return httpHeaders;
}
protected Map<String, String> createBasicAuthHeaders(String wso2User) throws Exception {
Map<String, String> 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;
}
}

View File

@ -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,6 +51,19 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess {
log.info("Found {} Apps", apps.getCount());
processApplications(apps, token);
if (adminsDefaultApplication != null) {
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);
throw new RuntimeException("Export failed", e);
}
}
private void processApplications(ArtifactSearchResults apps, TokenResponse token) throws InterruptedException {
int maxThreads = config.getMaxThreads();
ExecutorService executor = Executors.newFixedThreadPool(maxThreads);
@ -54,20 +73,10 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess {
}
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.MINUTES)) {
log.warn("Timeout waiting for App import tasks to finish");
if (!executor.awaitTermination(DEFAULT_TIMEOUT_MINUTES, TimeUnit.MINUTES)) {
log.warn("Timeout waiting for App export tasks to finish");
}
log.info("Finished processing Apps.");
if (adminsDefaultApplication != null) {
log.info("Found default application for admins: {}", adminsDefaultApplication.getName());
processApp(adminsDefaultApplication, token,
appCounter.getAndIncrement(), apps.getCount(), true);
}
} catch (Exception e) {
log.error("Error while exporting Apps.", e);
throw new RuntimeException("Export failed", e);
}
}
private void processApp(SearchedArtifact app, TokenResponse tokenResponse, int index, int total,
@ -81,27 +90,39 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess {
app.getId(), null, null);
for (SearchedVersion ver : versions.getVersions()) {
log.info(" - Found version: {}", ver.getVersion());
processAppVersion(app, tokenResponse, ver, createAdminApp);
}
long end = System.currentTimeMillis();
log.info("Finished processing App {} of {} in {} ms", index, total, (end - start));
} catch (Exception e) {
log.error("IO error while importing App {}: {}", app.getId(), e.getMessage(), e);
}
}
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);
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.");
continue;
return;
}
ApplicationCreateRequest appCreateRequest = mapAppDetailToCreateRequest(appDetail);
byte[] data = mapper.writeValueAsBytes(appCreateRequest);
log.info(" - Application {} with owner {} prepared for WSO2 import", appDetail.getName(),
log.info(" - Application {} with owner {} prepared for WSO2 export", appDetail.getName(),
appDetail.getOwner());
deleteWso2ApplicationIfExists(appDetail, tokenResponse);
@ -115,6 +136,38 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess {
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<String, Map<String, KeyManagerApp>> entry : appDetail
.getKeyManagerWiseOAuthApp().entrySet()) {
String keyType = entry.getKey();
Map<String, KeyManagerApp> keyManagerApp = entry.getValue();
for (Entry<String, KeyManagerApp> 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());
@ -123,17 +176,35 @@ public class ExportAppsToWso2FromV32 extends AbstractProcess {
}
}
} catch (Exception e) {
log.error("Error processing application version: {}", e.getMessage(), e);
}
}
long end = System.currentTimeMillis();
log.info("Finished processing App {} of {} in {} ms", index, total, (end - start));
private void publishApplicationKeyMappingToWso2(String applicationId, byte[] kmData, TokenResponse tokenResponse) {
try {
// Publish the application key mapping data to WSO2
Map<String, String> 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 importing App {}: {}", app.getId(), e.getMessage(), 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<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token()));
Map<String, String> 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<String, String> httpHeaders = new HashMap<>();
Map<String, String> httpHeaders = createBearerAuthHeaders(tokenResponse);
Map<String, String> 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<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("Authorization", "Basic ".concat(Base64.getEncoder()
.encodeToString(username.concat(":").concat(password).getBytes())));
Map<String, String> 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<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token()));
Map<String, String> httpHeaders = createBearerAuthHeaders(tokenResponse);
HttpResponse response = makeRequest("POST", endpoint, httpHeaders, null);

View File

@ -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<String, Object> parameters;
private Map<String, Object> parameters = new HashMap<String, Object>();
private boolean isSaasApplication;
private Map<String, Object> appAttributes;
private String appOwner;
private String jsonString;
private Map<String, String> appAttributes = new HashMap<>();
private String jsonAppAttribute;
private String applicationUUID;
private String tokenType;
}
}

View File

@ -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
}
}

View File

@ -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<String> 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<String, Object> additionalProperties;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Token {
@JsonProperty("accessToken")
private String accessToken;
@JsonProperty("tokenScopes")
private List<String> tokenScopes;
@JsonProperty("validityTime")
private Long validityTime;
}
enum Mode {
MAPPED, CREATED
}
enum KeyType {
PRODUCTION, SANDBOX
}
}