package cz.trask.migration; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.net.URLEncoder; import java.security.KeyStore; import java.text.Normalizer; import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import cz.trask.migration.config.ConfigManager; import cz.trask.migration.model.APIInfo; import cz.trask.migration.model.APIList; import cz.trask.migration.model.ApplicationConfig; import cz.trask.migration.model.ApplicationConfig.Wso2Settings; import cz.trask.migration.model.HttpResponse; import cz.trask.migration.model.RegisterResponse; import cz.trask.migration.model.TokenResponse; import cz.trask.migration.model.ZipEntryData; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.RegistryClientFactory; import io.apicurio.registry.rest.v2.beans.ArtifactMetaData; import io.apicurio.registry.rest.v2.beans.ArtifactReference; import io.apicurio.registry.rest.v2.beans.EditableMetaData; import io.apicurio.registry.rest.v2.beans.Rule; import io.apicurio.registry.types.RuleType; public abstract class AbstractProcess { private static Logger log = LogManager.getLogger(AbstractProcess.class); protected static final String PARAM_SOURCE_APIM = "source_apim"; protected static final String VERSION_32 = "v32"; public static final String ARTIFACT_GROUP_SUBSCRIPTIONS = "SUBSCRIPTIONS"; public static final String ARTIFACT_NAME_SUBSCRIPTIONS = "subs.yaml"; public static final String ARTIFACT_GROUP_APPLICATIONS = "APPLICATIONS"; public static final String ARTIFACT_APPLICATION_DEFAULT_VERSION = "1.0.0"; public static final String DEFAULT_APPLICATION_NAME = "DefaultApplication"; public static final String ADMIN_USERNAME = "admin"; public static final String DEFAULT_DOC_FILE_NAME = "document.yaml"; public static ObjectMapper mapper; public static ObjectMapper mapperYaml; public static Proxy proxy; public final RegistryClient client; protected ApplicationConfig config; protected AbstractProcess() { mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); YAMLFactory yamlFactory = new YAMLFactory(); yamlFactory.configure(YAMLGenerator.Feature.WRITE_DOC_START_MARKER, false); yamlFactory.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); yamlFactory.configure(YAMLGenerator.Feature.SPLIT_LINES, false); mapperYaml = new ObjectMapper(yamlFactory); mapperYaml.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); config = ConfigManager.getInstance().getConfig(); setTrustStoreCredentials(); if (config.getProxy() != null && config.getProxy().getHost() != null && !config.getProxy().getHost().isEmpty()) proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(config.getProxy().getHost(), config.getProxy().getPort())); javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() { public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) { return true; } }); Map clientConfigs = new HashMap<>(); try { SSLContext sslContext = createSSLContext( config.getTrustStore().getPath(), config.getTrustStore().getPassword() ); clientConfigs.put("io.apicurio.rest.client.jdk.sslContext", sslContext); } catch (Exception e) { log.error("Failed to initialize SSL context for Apicurio client", e); } this.client = RegistryClientFactory.create(config.getApicurio().getApiUrl(), clientConfigs); client.listConfigProperties(); } protected void setTrustStoreCredentials() { File trustStoreFile = new File(config.getTrustStore().getPath()); if (!trustStoreFile.exists() || !trustStoreFile.isFile()) { log.warn("Truststore file '{}' does not exist. Skipping truststore setup.", config.getTrustStore().getPath()); return; } log.info("Setting truststore: " + trustStoreFile.getAbsolutePath()); System.setProperty("javax.net.ssl.trustStore", trustStoreFile.getAbsolutePath()); System.setProperty("javax.net.ssl.trustStorePassword", config.getTrustStore().getPassword()); } private SSLContext createSSLContext(String trustStorePath, String trustStorePassword) throws Exception { // Vytvoříme TrustStore KeyStore trustStore = KeyStore.getInstance("JKS"); try (FileInputStream fis = new FileInputStream(trustStorePath)) { trustStore.load(fis, trustStorePassword.toCharArray()); } // Inicializujeme TrustManagerFactory TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); tmf.init(trustStore); // Vytvoříme SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); return sslContext; } protected void configureHttpsConnection(HttpsURLConnection connection) throws Exception { SSLContext sslContext = createSSLContext(System.getProperty("javax.net.ssl.trustStore"), System.getProperty("javax.net.ssl.trustStorePassword")); connection.setSSLSocketFactory(sslContext.getSocketFactory()); } protected TokenResponse authenticateToWso2AndGetToken(Wso2Settings endpoints) throws Exception { RegisterResponse register = register(endpoints.getRegistrationApiUrl(), endpoints.getWso2User()); String clientId = register.getClientId(); log.info("Registered with clientId: {}", clientId); 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: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()); return token; } /** * Retrieve access token based on clientId and clientSecret * * @param reg - client application object with clientId and clientSecret * @param scope - requested OAuth2 scope * @throws Exception */ protected TokenResponse getToken(String publisherurl, String wso2User, RegisterResponse reg, String scope) throws Exception { byte[] decoded = Base64.getDecoder().decode(wso2User); String decodedstring = new String(decoded); String[] decodedstringparts = decodedstring.split(":"); String username = decodedstringparts[0]; String password = decodedstringparts[1]; log.debug("Getting token with Username: '" + wso2User + "' URL: " + publisherurl); Map httpHeaders = new HashMap<>(); httpHeaders.put("Authorization", "Basic ".concat(Base64.getEncoder() .encodeToString(reg.getClientId().concat(":").concat(reg.getClientSecret()).getBytes()))); httpHeaders.put("Content-Type", "application/x-www-form-urlencoded"); String data = "grant_type=password&username=".concat(username).concat("&password=") .concat(URLEncoder.encode(password, "UTF-8")).concat("&scope=").concat(scope); HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data.getBytes()); log.debug("Token response: HTTP Code " + response.getResponseCode() + " Json: " + response.getResponse()); TokenResponse resp = mapper.readValue(response.getResponse(), TokenResponse.class); return resp; } /** * Register client application to get clientId and clientSecret * * @throws Exception */ protected RegisterResponse register(String publisherurl, String wso2User) throws Exception { log.debug("Registering with Username: '" + wso2User + "' URL: " + publisherurl); byte[] decodedUserBytes = Base64.getDecoder().decode(wso2User); String decodeduserappkey = new String(decodedUserBytes); String[] decodeduserparts = decodeduserappkey.split(":"); String decodeduser = decodeduserparts[0]; Map httpHeaders = new HashMap<>(); httpHeaders.put("Authorization", "Basic ".concat(wso2User)); httpHeaders.put("Content-Type", "application/json"); String data = "{\"callbackUrl\": \"www.google.lk\",\"clientName\": \"rest_api_publisher" + decodeduser + "\",\"owner\": \"" + decodeduser + "\",\"grantType\": \"password refresh_token\",\"saasApp\": true}"; HttpResponse response = makeDataRequest(publisherurl, httpHeaders, data.getBytes()); log.debug( "Register API response: HTTP Code " + response.getResponseCode() + " Json: " + response.getResponse()); RegisterResponse resp = mapper.readValue(response.getResponse(), RegisterResponse.class); return resp; } /** * Common function used for http request * * @param method - http method * @param urlStr - url to dev poral * @param httpHeaders * @param params - currently is not used * @throws Exception */ protected HttpResponse makeRequest(String method, String urlStr, Map httpHeaders, Map params) throws Exception { return makeRequest(method, urlStr, httpHeaders, params, false); } /** * Common function used for http request * * @param method - http method * @param urlStr - url to dev poral * @param httpHeaders * @param data - request data * @param binary - binary or text mode * @throws Exception */ protected HttpResponse makeRequest(String method, String urlStr, Map httpHeaders, Map params, boolean binary) throws Exception { log.info("Making {} request to URL: {}", method, urlStr); String query = ""; if (params != null) { for (String key : params.keySet()) { query = query.concat(URLEncoder.encode(key, "UTF-8")).concat("=") .concat(URLEncoder.encode(params.get(key), "UTF-8")).concat("&"); } } if (query.length() > 1 && "GET".equals(method)) { urlStr = urlStr.concat("?").concat(query); } URL url = new URL(urlStr); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(proxy!=null ? proxy : Proxy.NO_PROXY); con.setRequestMethod(method); con.setDoInput(true); configureHttpsConnection(con); for (String key : httpHeaders.keySet()) { con.addRequestProperty(key, httpHeaders.get(key)); } if (query.length() > 1 && "POST".equals(method)) { con.setDoOutput(true); OutputStream out = con.getOutputStream(); out.write(query.getBytes("UTF-8")); } InputStream in = con.getInputStream(); String res = ""; byte[] buf = new byte[4096]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); int read = in.read(buf); while (read != -1) { if (binary) baos.write(buf, 0, read); else res = res.concat(new String(buf, 0, read)); read = in.read(buf); } baos.flush(); HttpResponse resp = new HttpResponse(); resp.setHeaders(con.getHeaderFields()); if (binary) resp.setResponseBytes(baos.toByteArray()); else resp.setResponse(res); resp.setResponseCode(con.getResponseCode()); con.disconnect(); log.info("Response code: " + resp.getResponseCode()); // log.info("Response: " + resp.getResponse()); return resp; } /** * Common function used for http request * * @param urlStr - url to dev poral * @param httpHeaders * @param data - request data * @throws Exception */ protected HttpResponse makeDataRequest(String urlStr, Map httpHeaders, byte[] data) throws Exception { URL url = new URL(urlStr); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(proxy!=null ? proxy : Proxy.NO_PROXY); con.setRequestMethod("POST"); con.setDoInput(true); con.setDoOutput(true); configureHttpsConnection(con); for (String key : httpHeaders.keySet()) { con.addRequestProperty(key, httpHeaders.get(key)); } con.addRequestProperty("Content-Length", "" + data.length); OutputStream out = con.getOutputStream(); out.write(data); out.flush(); out.close(); InputStream in; try { in = con.getInputStream(); } catch (Exception e) { in = con.getErrorStream(); } if (in == null) { HttpResponse resp = new HttpResponse(); resp.setHeaders(con.getHeaderFields()); resp.setResponseCode(con.getResponseCode()); return resp; } String res = ""; byte[] buf = new byte[4096]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); int read = in.read(buf); while (read != -1) { res = res.concat(new String(buf, 0, read)); read = in.read(buf); } baos.flush(); HttpResponse resp = new HttpResponse(); resp.setHeaders(con.getHeaderFields()); resp.setResponse(res); resp.setResponseCode(con.getResponseCode()); return resp; } /** * Retrieve the list of APIs by name. * * @param tokenResponse - WSO2 APIM access token * @throws Exception */ protected APIList getList(String publisherurl, TokenResponse tokenResponse) throws Exception { APIList listOfApis = null; try { //String url = publisherurl.concat(String.format("/apis?limit=10&offset=0&query=name:%s", "PTSPaymentHubCZSIT*")); String url = publisherurl.concat("/apis?limit=9999&offset=0"); log.debug("Getting APIs with token: '" + tokenResponse.getAccess_token() + "' URL: " + url); Map httpHeaders = new HashMap<>(); Map params = new HashMap<>(); httpHeaders.put("Authorization", "Bearer ".concat(tokenResponse.getAccess_token())); HttpResponse response = makeRequest("GET", url, httpHeaders, params); log.debug("Listing APIs: HTTP Code " + response.getResponseCode() + " Data: " + response.getResponse()); listOfApis = mapper.readValue(response.getResponse(), APIList.class); if (response.getResponseCode() != 200) log.error("Cannot list API. Something bad happened."); } catch (Exception e) { log.error("Cannot list API:" + e); throw new Exception("Cannot list API:" + e.getMessage()); } return listOfApis; } /** * Common function used for upload API * * @param urlStr - url to dev poral * @param httpHeaders * @param params - currently is not used * @param api - zip file to upload * @throws Exception */ protected HttpResponse makeFileRequest(String method, String urlStr, Map httpHeaders, byte[] buff, String attachmentFileName) throws Exception { if (buff == null) { log.error("Cannot send NULL payload to rest service."); } String crlf = "\r\n"; String twoHyphens = "--"; String boundary = "----" + System.currentTimeMillis() + "----"; URL url = new URL(urlStr); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(proxy!=null ? proxy : Proxy.NO_PROXY); con.setUseCaches(false); con.setDoOutput(true); configureHttpsConnection(con); con.setRequestMethod(method); con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); con.setRequestProperty("User-Agent", "curl/7.55.1"); con.setRequestProperty("Accept", "*/*"); for (String key : httpHeaders.keySet()) { con.addRequestProperty(key, httpHeaders.get(key)); } DataOutputStream request = new DataOutputStream(con.getOutputStream()); request.writeBytes(crlf); request.writeBytes(twoHyphens + boundary + crlf); request.writeBytes( "Content-Disposition: form-data; name=\"file\"; filename=\"" + attachmentFileName + "\"" + crlf); request.writeBytes("Content-Type: application/octet-stream" + crlf); request.writeBytes("Content-Transfer-Encoding: binary" + crlf); request.writeBytes(crlf); request.write(buff); request.writeBytes(crlf); request.writeBytes(twoHyphens + boundary + twoHyphens + crlf); request.flush(); request.close(); String res = ""; byte[] buf = new byte[4096]; HttpResponse resp = new HttpResponse(); InputStream in; int responseCode = con.getResponseCode(); if (responseCode >= 200 || responseCode <= 299) { in = con.getInputStream(); } else { in = con.getErrorStream(); } while (in.available() > 0) { int read = in.read(buf); res = res.concat(new String(buf, 0, read)); } resp.setHeaders(con.getHeaderFields()); resp.setResponse(res); resp.setResponseCode(responseCode); return resp; } protected int publishApiToWso2(String fileName, byte[] data, TokenResponse tokenResponse) { int responseCode = -1; try { String url = config.getTarget().getPublisherApiUrl() .concat(String.format("?preserveProvider=false&overwrite=true")); log.info("API Import URL: " + url); Map httpHeaders = new HashMap<>(); httpHeaders.put("Authorization", "Bearer " + tokenResponse.getAccess_token()); HttpResponse response = makeFileRequest("POST", url, httpHeaders, data, fileName); responseCode = response.getResponseCode(); if (response.getResponseCode() != 201 && response.getResponseCode() != 200) { log.info("Cannot import API file: " + fileName + ", response code: " + response.getResponseCode()); } } catch (Exception e) { log.error("IO error while importing API file: " + fileName + ", error: " + e.getMessage(), e); } return responseCode; } protected int publishAppToWso2(String fileName, byte[] data, TokenResponse tokenResponse) { int responseCode = -1; try { String url = config.getTarget().getDevPortalApiUrl().concat(String.format( "/v3/applications/import?preserveOwner=true&skipSubscriptions=false&skipApplicationKeys=false&update=true")); log.info("App Import URL: " + url); Map httpHeaders = new HashMap<>(); httpHeaders.put("Authorization", "Bearer " + tokenResponse.getAccess_token()); HttpResponse response = makeFileRequest("POST", url, httpHeaders, data, fileName); responseCode = response.getResponseCode(); if (response.getResponseCode() < 200 || response.getResponseCode() > 299) { log.info("Cannot import App file: " + fileName + ", response code: " + response.getResponseCode()); } } catch (Exception e) { log.error("IO error while importing App file: " + fileName + ", error: " + e.getMessage(), e); } return responseCode; } protected void setArtifactMetaData(ArtifactMetaData meta, String name, String description, Map props) { EditableMetaData metaData = new EditableMetaData(); metaData.setName(name); metaData.setDescription(description); if (props != null) metaData.setProperties(props); client.updateArtifactMetaData(meta.getGroupId(), meta.getId(), metaData); } protected void createRule(ArtifactMetaData meta, String config, RuleType type) { Rule rule = new Rule(); rule.setConfig(config); 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; } protected void addEndpointsToProps(Map props, Map apiMap) { if (apiMap == null || !apiMap.containsKey("endpointURLs")) return; @SuppressWarnings("unchecked") List> envs = (List>) apiMap.get("endpointURLs"); for (Map env : envs) { @SuppressWarnings("unchecked") Map urls = (Map) env.get("URLs"); if (urls == null) continue; urls.forEach((k, v) -> { if (v != null) props.put(k + " Endpoint", v); }); } } protected void addTagsToProps(Map props, List tags) { if (tags != null && !tags.isEmpty()) { props.put("tags", String.join(", ", tags)); } } protected List createReferencesFromZip(List zipEntries, APIInfo api) throws IOException { List references = new ArrayList<>(); for (ZipEntryData entry : zipEntries) { String artifactId = convertToAscii(api.getName() + "/" + api.getVersion() + "/" + entry.getName()); log.debug("Creating artifact reference for entry: {} with artifactId: {}", entry.getName(), artifactId); File tmpFile = new File("tmp/api/", entry.getName()); FileOutputStream fos = new FileOutputStream(tmpFile); fos.write(entry.getContent()); fos.flush(); fos.close(); try (ByteArrayInputStream is = new ByteArrayInputStream(entry.getContent())) { ArtifactMetaData meta = client.createArtifactWithVersion(entry.getType().toString(), artifactId, api.getVersion(), is); Map props = new LinkedHashMap<>(); props.put(PARAM_SOURCE_APIM, VERSION_32); setArtifactMetaData(meta, entry.getName(), null, props); } ArtifactReference ref = new ArtifactReference(); ref.setName(entry.getName()); ref.setGroupId(entry.getType().toString()); ref.setArtifactId(artifactId); ref.setVersion(api.getVersion()); references.add(ref); } return references; } private String convertToAscii(String input) { if (input == null) { return null; } String normalized = Normalizer.normalize(input, Normalizer.Form.NFD); return normalized.replaceAll("[^\\x00-\\x7F]", "").replace(" ", "_"); } }