All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.axway.apim.apiimport.APIImportConfigAdapter Maven / Gradle / Ivy

package com.axway.apim.apiimport;

import com.axway.apim.adapter.APIManagerAdapter;
import com.axway.apim.adapter.apis.APIManagerOAuthClientProfilesAdapter;
import com.axway.apim.adapter.apis.APIManagerOrganizationAdapter;
import com.axway.apim.adapter.client.apps.ClientAppFilter;
import com.axway.apim.adapter.jackson.QuotaRestrictionDeserializer;
import com.axway.apim.adapter.jackson.QuotaRestrictionDeserializer.DeserializeMode;
import com.axway.apim.api.API;
import com.axway.apim.api.model.*;
import com.axway.apim.api.model.CustomProperties.Type;
import com.axway.apim.api.model.apps.ClientApplication;
import com.axway.apim.api.specification.APISpecification;
import com.axway.apim.api.specification.APISpecificationFactory;
import com.axway.apim.apiimport.lib.params.APIImportParams;
import com.axway.apim.lib.APIPropertiesExport;
import com.axway.apim.lib.CoreParameters;
import com.axway.apim.lib.EnvironmentProperties;
import com.axway.apim.lib.error.AppException;
import com.axway.apim.lib.error.ErrorCode;
import com.axway.apim.lib.utils.Utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;

/**
 * The APIConfig reflects the given API-Configuration plus the API-Definition, which is either a
 * Swagger-File or a WSDL.
 * This class will read the API-Configuration plus the optional set stage and the API-Definition.
 *
 * @author cwiechmann
 */
public class APIImportConfigAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(APIImportConfigAdapter.class);
    public static final String DEFAULT = "_default";
    public static final String ORIGINAL = "original";
    public static final String MANUAL = "manual";
    public static final String VALIDATE_ORGANIZATION = "validateOrganization";
    public static final String EXCEPTION = "Exception: ";
    public static final String FROM_FILESYSTEM_OR_CLASSPATH = " from filesystem or classpath.";

    /**
     * This is the given path to WSDL or Swagger. It is either set using -a parameter or as part of the config file
     */
    private String pathToAPIDefinition;

    /**
     * The API-Config-File given by the user with -c parameter
     */
    private final File apiConfigFile;

    /**
     * The APIConfig instance created by the APIConfigImporter
     */
    private API apiConfig;

    public APIImportConfigAdapter(APIImportParams params) throws AppException {
        this(params.getConfig(), params.getStage(), params.getApiDefinition(), params.getStageConfig());
    }

    /**
     * Constructs the APIImportConfig
     *
     * @param apiConfigFileName   the API-Config given by the user
     * @param stage               an optional stage used to load overrides and stage specific environment properties
     * @param pathToAPIDefinition an optional path to the API-Definition (Swagger / WSDL), can be in the config-file as well.
     * @param stageConfig         a stage config string
     * @throws AppException if the config-file can't be parsed for some reason
     */
    public APIImportConfigAdapter(String apiConfigFileName, String stage, String pathToAPIDefinition, String stageConfig) throws AppException {
        try {
            this.pathToAPIDefinition = pathToAPIDefinition;
            this.apiConfigFile = Utils.locateConfigFile(apiConfigFileName);
            File stageConfigFile = Utils.getStageConfig(stage, stageConfig, this.apiConfigFile);
            // Validate organization for the base config, if no staged-config is given
            boolean validateOrganization = stageConfigFile == null;
            ObjectMapper mapper = Utils.createObjectMapper(apiConfigFile);
            SimpleModule module = new SimpleModule();
            module.addDeserializer(QuotaRestriction.class, new QuotaRestrictionDeserializer(DeserializeMode.configFile, false));
            // We would like to get back the original AppExcepption instead of a JsonMappingException
            mapper.disable(DeserializationFeature.WRAP_EXCEPTIONS);
            mapper.registerModule(module);
            mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
            ObjectReader reader = mapper.reader();
            API baseConfig = reader.withAttribute(VALIDATE_ORGANIZATION, validateOrganization).forType(DesiredAPI.class).readValue(Utils.substituteVariables(this.apiConfigFile));
            if (stageConfigFile != null) {
                readConfig(mapper, baseConfig, stageConfigFile, stage);
            } else {
                apiConfig = baseConfig;
            }
        } catch (MismatchedInputException e) {
            if (e.getMessage().contains("com.axway.apim.api.model.APISpecIncludeExcludeFilter")) {
                throw new AppException("An error occurred while reading the API specification filters. Please note that the filter structure has changed "
                    + "between version 1.8.0 and 1.9.0. You can find more information here: "
                    + "https://github.com/Axway-API-Management-Plus/apim-cli/wiki/2.1.10-API-Specification#filter-api-specifications", ErrorCode.CANT_READ_CONFIG_FILE, e);
            } else {
                throw new AppException("Error reading API-Config file(s)", EXCEPTION + e.getClass().getName() + ": " + e.getMessage(), ErrorCode.CANT_READ_CONFIG_FILE, e);
            }
        } catch (IOException e) {
            throw new AppException("Cannot parse API-Config file(s).", EXCEPTION + e.getClass().getName() + ": " + e.getMessage(), ErrorCode.CANT_READ_JSON_PAYLOAD, e);
        }
    }

    public API getApiConfig() {
        return apiConfig;
    }

    /**
     * Returns the IAPIDefintion that returns the desired state of the API. In this method:
* - the API-Config is read * - the API-Config is merged with the override * - the API-Definition is read * - Additionally some validations and completions are made here * - in the future: This is the place to do some default handling. * * @return IAPIDefintion with the desired state of the API. This state will be * the input to create the APIChangeState. * @throws AppException if the state can't be created. */ public API getDesiredAPI() throws AppException { try { validateExposurePath(apiConfig); validateOrganization(apiConfig); addAPISpecification(apiConfig); addDefaultPassthroughSecurityProfile(apiConfig); addDefaultAuthenticationProfile(apiConfig); validateOutboundProfile(apiConfig); validateInboundProfile(apiConfig); addImageContent(apiConfig); Utils.validateCustomProperties(apiConfig.getCustomProperties(), Type.api); validateDescription(apiConfig); validateOutboundAuthN(apiConfig); addDefaultCorsProfile(apiConfig); validateHasQueryStringKey(apiConfig); completeCaCerts(apiConfig); addQuotaConfiguration(apiConfig); handleAllOrganizations(apiConfig); completeClientApplications(apiConfig); handleVhost(apiConfig); validateMethodDescription(apiConfig.getApiMethods()); return apiConfig; } catch (AppException e) { throw e; } catch (Exception e) { if (e.getCause() instanceof AppException) { throw (AppException) e.getCause(); } throw new AppException("Cannot validate/fulfill configuration file.", ErrorCode.CANT_READ_CONFIG_FILE, e); } } private void validateExposurePath(API apiConfig) throws AppException { if (apiConfig.getPath() == null) { throw new AppException("API-Config parameter: 'path' is missing. Please check your API-Config file.", ErrorCode.CANT_READ_CONFIG_FILE); } if (!apiConfig.getPath().startsWith("/")) { throw new AppException("API-Config parameter: 'path' must start with a \"/\" following by a valid API-Path (e.g. /api/v1/customer).", ErrorCode.CANT_READ_CONFIG_FILE); } } private void validateOrganization(API apiConfig) throws AppException { if (apiConfig.getOrganization() == null || !apiConfig.getOrganization().isDevelopment()) { throw new AppException("The given organization: '" + apiConfig.getOrganization() + "' is either unknown or hasn't the Development flag.", ErrorCode.UNKNOWN_ORGANIZATION); } } private void addAPISpecification(API apiConfig) throws IOException { APISpecification apiSpecification; if (((DesiredAPI) apiConfig).getDesiredAPISpecification() != null) { // API-Specification object that might contain filters, the type of an API, etc. apiSpecification = APISpecificationFactory.getAPISpecification(((DesiredAPI) apiConfig).getDesiredAPISpecification(), this.apiConfigFile.getCanonicalFile().getParent(), apiConfig.getName()); } else if (StringUtils.isNotEmpty(this.pathToAPIDefinition)) { apiSpecification = APISpecificationFactory.getAPISpecification(this.pathToAPIDefinition, this.apiConfigFile.getCanonicalFile().getParent(), apiConfig.getName()); } else if (StringUtils.isNotEmpty(apiConfig.getApiDefinitionImport())) { apiSpecification = APISpecificationFactory.getAPISpecification(apiConfig.getApiDefinitionImport(), this.apiConfigFile.getCanonicalFile().getParent(), apiConfig.getName()); this.pathToAPIDefinition = apiConfig.getApiDefinitionImport(); LOG.debug("Reading API Definition from configuration file"); } else { throw new AppException("No API Specification configured", ErrorCode.NO_API_DEFINITION_CONFIGURED); } apiSpecification.configureBasePath(((DesiredAPI) apiConfig).getBackendBasepath(), apiConfig); apiConfig.setApiDefinition(apiSpecification); } private void handleAllOrganizations(API apiConfig) throws AppException { if (apiConfig.getClientOrganizations() == null) return; if (apiConfig.getState().equals(API.STATE_UNPUBLISHED)) { apiConfig.setClientOrganizations(null); // Making sure, orgs are not considered as a changed property return; } APIManagerOrganizationAdapter organizationAdapter = APIManagerAdapter.getInstance().getOrgAdapter(); if (apiConfig.getClientOrganizations().contains(new Organization.Builder().hasName("ALL").build())) { List allOrgs = organizationAdapter.getAllOrgs(); apiConfig.getClientOrganizations().clear(); apiConfig.getClientOrganizations().addAll(allOrgs); apiConfig.setRequestForAllOrgs(true); } else { // As the API-Manager internally handles the owning organization in the same way, // we have to add the Owning-Org as a desired org if (!apiConfig.getClientOrganizations().contains(apiConfig.getOrganization())) { apiConfig.getClientOrganizations().add(apiConfig.getOrganization()); } // And validate each configured organization really exists in the API-Manager Iterator it = apiConfig.getClientOrganizations().iterator(); String invalidClientOrgs = null; List foundOrgs = new ArrayList<>(); while (it.hasNext()) { Organization desiredOrg = it.next(); Organization org = organizationAdapter.getOrgForName(desiredOrg.getName()); if (org == null) { LOG.warn("Unknown organization with name: {} configured. Ignoring this organization.", desiredOrg.getName()); invalidClientOrgs = invalidClientOrgs == null ? desiredOrg.getName() : invalidClientOrgs + ", " + desiredOrg.getName(); APIPropertiesExport.getInstance().setProperty(ErrorCode.INVALID_CLIENT_ORGANIZATIONS.name(), invalidClientOrgs); it.remove(); continue; } it.remove(); foundOrgs.add(org); } apiConfig.getClientOrganizations().addAll(foundOrgs); } } private void addQuotaConfiguration(API apiConfig) { if (apiConfig.getState().equals(API.STATE_UNPUBLISHED)) return; initQuota(apiConfig.getSystemQuota()); initQuota(apiConfig.getApplicationQuota()); } private void initQuota(APIQuota quotaConfig) { if (quotaConfig == null) return; if (quotaConfig.getType().equals("APPLICATION")) { quotaConfig.setName("Application Default"); quotaConfig.setDescription("Maximum message rates per application. Applied to each application unless an Application-Specific quota is configured"); } else { quotaConfig.setName("System Default"); quotaConfig.setDescription("....."); } } private void validateDescription(API apiConfig) throws AppException { if (apiConfig.getDescriptionType() == null || apiConfig.getDescriptionType().equals(ORIGINAL)) return; String descriptionType = apiConfig.getDescriptionType(); switch (descriptionType) { case MANUAL: if (apiConfig.getDescriptionManual() == null) { throw new AppException("descriptionManual can't be null with descriptionType set to 'manual'", ErrorCode.CANT_READ_CONFIG_FILE); } break; case "url": if (apiConfig.getDescriptionUrl() == null) { throw new AppException("descriptionUrl can't be null with descriptionType set to 'url'", ErrorCode.CANT_READ_CONFIG_FILE); } break; case "markdown": if (apiConfig.getDescriptionMarkdown() == null) { throw new AppException("descriptionMarkdown can't be null with descriptionType set to 'markdown'", ErrorCode.CANT_READ_CONFIG_FILE); } if (!apiConfig.getDescriptionMarkdown().startsWith("${env.")) { throw new AppException("descriptionMarkdown must start with an environment variable", ErrorCode.CANT_READ_CONFIG_FILE); } break; case "markdownLocal": if (apiConfig.getMarkdownLocal() == null) { throw new AppException("markdownLocal can't be null with descriptionType set to 'markdownLocal'", ErrorCode.CANT_READ_CONFIG_FILE); } try { StringBuilder markdownDescription = new StringBuilder(); String newLine = ""; for (String markdownFilename : apiConfig.getMarkdownLocal()) { if ("ORIGINAL".equals(markdownFilename)) { markdownDescription.append(newLine).append(apiConfig.getApiDefinition().getDescription()); } else { File markdownFile = new File(markdownFilename); if (!markdownFile.exists()) { // The file isn't provided with an absolute path, try to read it relative to the config file LOG.trace("Error reading markdown description file (absolute): {}", markdownFile.getCanonicalPath()); String baseDir = this.apiConfigFile.getCanonicalFile().getParent(); markdownFile = new File(baseDir, markdownFilename); } if (!markdownFile.exists()) { LOG.trace("Error reading markdown description file (relative): {}", markdownFile.getCanonicalPath()); throw new AppException("Error reading markdown description file: " + markdownFilename, ErrorCode.CANT_READ_CONFIG_FILE); } LOG.debug("Reading local markdown description file: {}", markdownFile.getPath()); markdownDescription.append(newLine).append(new String(Files.readAllBytes(markdownFile.toPath()), StandardCharsets.UTF_8)); } newLine = "\n"; } apiConfig.setDescriptionManual(markdownDescription.toString()); apiConfig.setDescriptionType(MANUAL); } catch (IOException e) { throw new AppException("Error reading markdown description file: " + apiConfig.getMarkdownLocal(), ErrorCode.CANT_READ_CONFIG_FILE, e); } break; case ORIGINAL: break; default: throw new AppException("Unknown descriptionType: '" + descriptionType + "'", ErrorCode.CANT_READ_CONFIG_FILE); } } private void validateMethodDescription(List apiMethods) throws AppException { if (apiMethods == null) return; for (APIMethod apiMethod : apiMethods) { String descriptionType = apiMethod.getDescriptionType(); if (descriptionType == null) { throw new AppException("apiMethods descriptionType can't be null set default value as 'original'", ErrorCode.CANT_READ_CONFIG_FILE); } switch (descriptionType) { case ORIGINAL: return; case MANUAL: if (apiMethod.getDescriptionManual() == null) { throw new AppException("apiMethods descriptionManual can't be null with descriptionType set to 'manual'", ErrorCode.CANT_READ_CONFIG_FILE); } break; case "url": if (apiMethod.getDescriptionUrl() == null) { throw new AppException("apiMethods descriptionUrl can't be null with descriptionType set to 'url'", ErrorCode.CANT_READ_CONFIG_FILE); } break; case "markdown": if (apiMethod.getDescriptionMarkdown() == null) { throw new AppException("apiMethods descriptionMarkdown can't be null with descriptionType set to 'markdown'", ErrorCode.CANT_READ_CONFIG_FILE); } if (!apiMethod.getDescriptionMarkdown().startsWith("${env.")) { throw new AppException("apiMethods descriptionMarkdown must start with an environment variable", ErrorCode.CANT_READ_CONFIG_FILE); } break; default: throw new AppException("apiMethods Unknown descriptionType: '" + descriptionType + "'", ErrorCode.CANT_READ_CONFIG_FILE); } } } private void addDefaultCorsProfile(API apiConfig) { if (apiConfig.getCorsProfiles() == null) { apiConfig.setCorsProfiles(new ArrayList<>()); } // Check if there is a default cors profile declared otherwise create one internally boolean defaultCorsFound = false; for (CorsProfile profile : apiConfig.getCorsProfiles()) { if (profile.getName().equals(DEFAULT)) { defaultCorsFound = true; break; } } if (apiConfig.getCorsProfiles().size() == 1) { // Make this CORS-Profile default, even if it's not named default apiConfig.getInboundProfiles().get(DEFAULT).setCorsProfile(apiConfig.getCorsProfiles().get(0).getName()); } if (!defaultCorsFound) { apiConfig.getCorsProfiles().add(CorsProfile.getDefaultCorsProfile()); } } /** * Purpose of this method is to load the actual existing applications from API-Manager * based on the provided criteria (App-Name, API-Key, OAuth-ClientId or Ext-ClientId). * Or, if the APP doesn't exists remove it from the list and log a warning message. * Additionally, for each application it's checked, that the organization has access * to this API, otherwise it will be removed from the list as well and a warning message is logged. * * @param apiConfig apiConfig * @throws AppException AppException */ private void completeClientApplications(API apiConfig) throws AppException { if (CoreParameters.getInstance().isIgnoreClientApps()) return; ClientApplication loadedApp = null; ClientApplication app; if (apiConfig.getApplications() != null) { LOG.info("Handling configured client-applications."); ListIterator it = apiConfig.getApplications().listIterator(); String invalidClientApps = null; while (it.hasNext()) { app = it.next(); if (app.getName() != null) { ClientAppFilter filter = new ClientAppFilter.Builder().hasName(app.getName()).build(); loadedApp = APIManagerAdapter.getInstance().getAppAdapter().getApplication(filter); if (loadedApp == null) { LOG.warn("Unknown application with name: {} configured. Ignoring this application.", filter.getApplicationName()); invalidClientApps = invalidClientApps == null ? app.getName() : invalidClientApps + ", " + app.getName(); APIPropertiesExport.getInstance().setProperty(ErrorCode.INVALID_CLIENT_APPLICATIONS.name(), invalidClientApps); it.remove(); continue; } LOG.info("Found existing application: {} ({}) based on given name {}", app.getName(), app.getId(), app.getName()); } else if (app.getApiKey() != null) { loadedApp = getAppForCredential(app.getApiKey(), APIManagerAdapter.CREDENTIAL_TYPE_API_KEY); if (loadedApp == null) { it.remove(); continue; } } else if (app.getOauthClientId() != null) { loadedApp = getAppForCredential(app.getOauthClientId(), APIManagerAdapter.CREDENTIAL_TYPE_OAUTH); if (loadedApp == null) { it.remove(); continue; } } else if (app.getExtClientId() != null) { loadedApp = getAppForCredential(app.getExtClientId(), APIManagerAdapter.CREDENTIAL_TYPE_EXT_CLIENTID); if (loadedApp == null) { it.remove(); continue; } } if (!APIManagerAdapter.getInstance().hasAdminAccount() && (!apiConfig.getOrganization().equals(loadedApp != null ? loadedApp.getOrganization() : null))) { LOG.warn("OrgAdmin can't handle application: {} belonging to a different organization. Ignoring this application.", loadedApp != null ? loadedApp.getName() : null); it.remove(); continue; } it.set(loadedApp); // Replace the incoming app, with the App loaded from API-Manager } } } private static ClientApplication getAppForCredential(String credential, String type) throws AppException { LOG.debug("Searching application with configured credential (Type: {}): {}", type, credential); ClientApplication app = APIManagerAdapter.getInstance().getAppIdForCredential(credential, type); if (app == null) { LOG.warn("Unknown application with ({}): {} configured. Ignoring this application.", type, credential); return null; } return app; } public void completeCaCerts(API apiConfig) throws AppException { ObjectMapper mapper = new ObjectMapper(); if (apiConfig.getCaCerts() != null) { List completedCaCerts = new ArrayList<>(); for (CaCert cert : apiConfig.getCaCerts()) { if (cert.getCertBlob() == null) { try (InputStream is = getInputStreamForCertFile(cert)) { String certInfo = APIManagerAdapter.getCertInfo(is, "", cert); List completedCerts = mapper.readValue(certInfo, new TypeReference>() { }); completedCaCerts.addAll(completedCerts); } catch (Exception e) { throw new AppException("Can't initialize given certificate.", ErrorCode.CANT_READ_CONFIG_FILE, e); } } } apiConfig.getCaCerts().clear(); apiConfig.getCaCerts().addAll(completedCaCerts); } } private InputStream getInputStreamForCertFile(CaCert cert) throws AppException { InputStream is; // Handel base64 encoded inline certificate if (cert.getCertFile().startsWith("data:")) { byte[] data = Base64.getDecoder().decode(cert.getCertFile().replaceFirst("data:.+,", "")); return new ByteArrayInputStream(data); } // Certificates might be stored somewhere else, so try to load them directly File file = new File(cert.getCertFile()); if (file.exists()) { try { is = new FileInputStream(file); return is; } catch (FileNotFoundException e) { throw new AppException("Cant read given certificate file", ErrorCode.CANT_READ_CONFIG_FILE); } } String baseDir; try { baseDir = this.apiConfigFile.getCanonicalFile().getParent(); } catch (IOException e1) { throw new AppException("Can't read certificate file.", ErrorCode.CANT_READ_CONFIG_FILE, e1); } file = new File(baseDir + File.separator + cert.getCertFile()); if (file.exists()) { try { is = new FileInputStream(file); } catch (FileNotFoundException e) { throw new AppException("Cant read given certificate file", ErrorCode.CANT_READ_CONFIG_FILE); } } else { LOG.debug("Can't read certifiate from file-location: {}. Now trying to read it from the classpath.", file); // Try to read it from classpath is = APIManagerAdapter.class.getResourceAsStream(cert.getCertFile()); } if (is == null) { LOG.error("Can't read certificate: {} from file or classpath.", cert.getCertFile()); LOG.error("Certificates in filesystem are either expected relative to the API-Config-File or as an absolute path."); LOG.error("In the same directory - Example: \"myCertFile.crt\""); LOG.error("Relative to it - Example: \"../../allMyCertsAreHere/myCertFile.crt\""); LOG.error("With an absolute path - Example: \"/another/location/with/allMyCerts/myCertFile.crt\""); throw new AppException("Can't read certificate: " + cert.getCertFile() + " from file or classpath.", ErrorCode.CANT_READ_CONFIG_FILE); } return is; } private void validateInboundProfile(API importApi) throws AppException { if (importApi.getInboundProfiles() == null || importApi.getInboundProfiles().isEmpty()) { Map def = new HashMap<>(); def.put(DEFAULT, InboundProfile.getDefaultInboundProfile()); importApi.setInboundProfiles(def); return; } Iterator it = importApi.getInboundProfiles().keySet().iterator(); // Check if a default inbound profile is given boolean defaultProfileFound = false; while (it.hasNext()) { String profileName = it.next(); if (profileName.equals(DEFAULT)) { defaultProfileFound = true; continue; // No need to check for the default profile } // Check the referenced profiles are valid InboundProfile profile = importApi.getInboundProfiles().get(profileName); if (profile.getCorsProfile() != null && getCorsProfile(importApi, profile.getCorsProfile()) == null) { throw new AppException("Inbound profile is referencing a unknown CorsProfile: '" + profile.getCorsProfile() + "'", ErrorCode.REFERENCED_PROFILE_INVALID); } if (profile.getSecurityProfile() != null && getSecurityProfile(importApi, profile.getSecurityProfile()) == null) { throw new AppException("Inbound profile is referencing a unknown SecurityProfile: '" + profile.getSecurityProfile() + "'", ErrorCode.REFERENCED_PROFILE_INVALID); } } /// If not, create a PassThrough! if (!defaultProfileFound) { InboundProfile defaultProfile = new InboundProfile(); defaultProfile.setSecurityProfile(DEFAULT); defaultProfile.setCorsProfile(DEFAULT); defaultProfile.setMonitorAPI(true); defaultProfile.setMonitorSubject("authentication.subject.id"); importApi.getInboundProfiles().put(DEFAULT, defaultProfile); } } private void addDefaultPassthroughSecurityProfile(API importApi) throws AppException { boolean hasDefaultProfile = false; if (importApi.getSecurityProfiles() == null) importApi.setSecurityProfiles(new ArrayList<>()); List profiles = importApi.getSecurityProfiles(); for (SecurityProfile profile : importApi.getSecurityProfiles()) { if (profile.getIsDefault() || profile.getName().equals(DEFAULT)) { if (hasDefaultProfile) { throw new AppException("You can have only one _default SecurityProfile.", ErrorCode.CANT_READ_CONFIG_FILE); } hasDefaultProfile = true; // If the name is _default or flagged as default make it consistent! profile.setName(DEFAULT); profile.setIsDefault(true); } } if (profiles.isEmpty() || !hasDefaultProfile) { SecurityProfile passthroughProfile = new SecurityProfile(); passthroughProfile.setName(DEFAULT); passthroughProfile.setIsDefault(true); SecurityDevice passthroughDevice = new SecurityDevice(); passthroughDevice.setName("Pass Through"); passthroughDevice.setType(DeviceType.passThrough); passthroughDevice.setOrder(0); passthroughDevice.getProperties().put("subjectIdFieldName", "Pass Through"); passthroughDevice.getProperties().put("removeCredentialsOnSuccess", "true"); passthroughProfile.getDevices().add(passthroughDevice); profiles.add(passthroughProfile); } } private void addDefaultAuthenticationProfile(API importApi) throws AppException { if (importApi.getAuthenticationProfiles() == null) return; // Nothing to add (no default is needed, as we don't send any Authn-Profile) boolean hasDefaultProfile = false; List profiles = importApi.getAuthenticationProfiles(); for (AuthenticationProfile profile : profiles) { if (profile.getIsDefault() || profile.getName().equals(DEFAULT)) { if (hasDefaultProfile) { throw new AppException("You can have only one AuthenticationProfile configured as default", ErrorCode.CANT_READ_CONFIG_FILE); } hasDefaultProfile = true; // If the name is _default or flagged as default make it consistent! profile.setName(DEFAULT); profile.setIsDefault(true); } } if (!hasDefaultProfile) { LOG.warn("THERE IS NO DEFAULT authenticationProfile CONFIGURED. Auto-Creating a No-Authentication outbound profile as default!"); AuthenticationProfile noAuthNProfile = new AuthenticationProfile(); noAuthNProfile.setName(DEFAULT); noAuthNProfile.setIsDefault(true); noAuthNProfile.setType(AuthType.none); profiles.add(noAuthNProfile); } } private void validateOutboundProfile(API importApi) throws AppException { if (importApi.getOutboundProfiles() == null || importApi.getOutboundProfiles().isEmpty()) return; Iterator it = importApi.getOutboundProfiles().keySet().iterator(); boolean defaultProfileFound = false; while (it.hasNext()) { String profileName = it.next(); OutboundProfile profile = importApi.getOutboundProfiles().get(profileName); if (profileName.equals(DEFAULT)) { defaultProfileFound = true; // Validate the _default Outbound-Profile has an AuthN-Profile, otherwise we must add (See issue #133) if (profile.getAuthenticationProfile() == null) { LOG.warn("Provided default outboundProfile doesn't contain AuthN-Profile - Setting it to default"); profile.setAuthenticationProfile(DEFAULT); } } // Check the referenced authentication profile exists if (!profile.getAuthenticationProfile().equals(DEFAULT) && (profile.getAuthenticationProfile() != null && getAuthNProfile(importApi, profile.getAuthenticationProfile()) == null)) { throw new AppException("OutboundProfile is referencing a unknown AuthenticationProfile: '" + profile.getAuthenticationProfile() + "'", ErrorCode.REFERENCED_PROFILE_INVALID); } // Check a routingPolicy is given, if routeType is policy if ("policy".equals(profile.getRouteType()) && profile.getRoutePolicy() == null) { throw new AppException("Missing routingPolicy when routeType is set to policy", ErrorCode.CANT_READ_CONFIG_FILE); } } if (!defaultProfileFound) { OutboundProfile defaultProfile = new OutboundProfile(); defaultProfile.setAuthenticationProfile(DEFAULT); defaultProfile.setRouteType("proxy"); importApi.getOutboundProfiles().put(DEFAULT, defaultProfile); } } private void validateOutboundAuthN(API importApi) throws AppException { // Request to use some specific Outbound-AuthN for this API if (importApi.getAuthenticationProfiles() == null || importApi.getAuthenticationProfiles().isEmpty()) return; for (AuthenticationProfile authProfile : importApi.getAuthenticationProfiles()) { if (authProfile.getType().equals(AuthType.ssl)) { handleOutboundSSLAuthN(authProfile); } else if (authProfile.getType().equals(AuthType.oauth)) { handleOutboundOAuthAuthN(authProfile); } } } private void handleOutboundOAuthAuthN(AuthenticationProfile authnProfile) throws AppException { if (!authnProfile.getType().equals(AuthType.oauth)) return; String providerProfile = (String) authnProfile.getParameters().get("providerProfile"); if (providerProfile != null && providerProfile.startsWith(" knownProfiles = new ArrayList<>(); for (OAuthClientProfile profile : oAuthClientProfilesAdapter.getOAuthClientProfiles()) { knownProfiles.add(profile.getName()); } throw new AppException("The OAuth provider profile is unkown: '" + providerProfile + "'. Known profiles: " + knownProfiles, ErrorCode.REFERENCED_PROFILE_INVALID); } authnProfile.getParameters().put("providerProfile", clientProfile.getId()); } private void handleOutboundSSLAuthN(AuthenticationProfile authnProfile) throws AppException { if (!authnProfile.getType().equals(AuthType.ssl)) return; if (EnvironmentProperties.PRINT_CONFIG_CONSOLE) return; String keystore = (String) authnProfile.getParameters().get("certFile"); String password = (String) authnProfile.getParameters().get("password"); File clientCertFile = new File(keystore); try { if (!clientCertFile.exists()) { // Try to find file using a relative path to the config file String baseDir = this.apiConfigFile.getCanonicalFile().getParent(); clientCertFile = new File(baseDir, keystore); } if (!clientCertFile.exists()) { // If not found absolute & relative - Try to load it from ClassPath LOG.debug("Trying to load Client-Certificate from classpath"); if (this.getClass().getResource(keystore) == null) { throw new AppException("Can't read Client-Certificate-Keystore: " + keystore + FROM_FILESYSTEM_OR_CLASSPATH, ErrorCode.UNXPECTED_ERROR); } clientCertFile = new File(Objects.requireNonNull(this.getClass().getResource(keystore)).getFile()); } JsonNode fileData; try (InputStream inputStream = Files.newInputStream(clientCertFile.toPath())) { fileData = APIManagerAdapter.getFileData(IOUtils.toByteArray(inputStream), keystore, ContentType.create("application/x-pkcs12")); } CaCert cert = new CaCert(); cert.setCertFile(clientCertFile.getName()); cert.setInbound("false"); cert.setOutbound("true"); // This call is to validate the given password, keystore is valid APIManagerAdapter.getCertInfo(Files.newInputStream(clientCertFile.toPath()), password, cert); String data = fileData.get("data").asText(); authnProfile.getParameters().put("pfx", data); authnProfile.getParameters().remove("certFile"); } catch (Exception e) { throw new AppException("Can't read Client-Cert-File: " + keystore + FROM_FILESYSTEM_OR_CLASSPATH, ErrorCode.UNXPECTED_ERROR, e); } } private void validateHasQueryStringKey(API importApi) throws AppException { if (importApi.getApiRoutingKey() == null) return; // Nothing to check if (APIManagerAdapter.getInstance().hasAdminAccount()) { boolean apiRoutingKeyEnabled = APIManagerAdapter.getInstance().getConfigAdapter().getConfig(true).getApiRoutingKeyEnabled(); if (!apiRoutingKeyEnabled) { throw new AppException("API-Manager Query-String Routing option is disabled. Please turn it on to use apiRoutingKey.", ErrorCode.QUERY_STRING_ROUTING_DISABLED); } } else { LOG.debug("Can't check if QueryString for API is needed without Admin-Account."); } } private void addImageContent(API importApi) throws AppException { // No image declared do nothing if (importApi.getImage() == null) return; File file; try { file = new File(importApi.getImage().getFilename()); if (!file.exists()) { // The image isn't provided with an absolute path, try to read it relative to the config file String baseDir = this.apiConfigFile.getCanonicalFile().getParent(); file = new File(baseDir, importApi.getImage().getFilename()); } importApi.getImage().setBaseFilename(file.getName()); if (file.exists()) { LOG.info("Loading image from: {}", file.getCanonicalFile()); try (InputStream is = Files.newInputStream(file.toPath())) { importApi.getImage().setImageContent(IOUtils.toByteArray(is)); return; } } // Try to read it from classpath try (InputStream is = this.getClass().getResourceAsStream(importApi.getImage().getFilename())) { if (is != null) { LOG.debug("Trying to load image from classpath"); importApi.getImage().setImageContent(IOUtils.toByteArray(is)); return; } } // An image is configured, but not found throw new AppException("Configured image: '" + importApi.getImage().getFilename() + "' not found in filesystem (Relative/Absolute) or classpath.", ErrorCode.UNXPECTED_ERROR); } catch (Exception e) { throw new AppException("Can't read configured image-file: " + importApi.getImage().getFilename() + FROM_FILESYSTEM_OR_CLASSPATH, ErrorCode.UNXPECTED_ERROR, e); } } /* * Refactor the following three method a Generic one */ private CorsProfile getCorsProfile(API api, String profileName) { if (api.getCorsProfiles() == null || api.getCorsProfiles().isEmpty()) return null; for (CorsProfile cors : api.getCorsProfiles()) { if (profileName.equals(cors.getName())) return cors; } return null; } private AuthenticationProfile getAuthNProfile(API api, String profileName) { if (api.getAuthenticationProfiles() == null || api.getAuthenticationProfiles().isEmpty()) return null; for (AuthenticationProfile profile : api.getAuthenticationProfiles()) { if (profileName.equals(profile.getName())) return profile; } return null; } private SecurityProfile getSecurityProfile(API api, String profileName) { if (api.getSecurityProfiles() == null || api.getSecurityProfiles().isEmpty()) return null; for (SecurityProfile profile : api.getSecurityProfiles()) { if (profileName.equals(profile.getName())) return profile; } return null; } private void handleVhost(API apiConfig) { if (apiConfig.getVhost() == null) return; // Consider an empty VHost as not set, as it is logically not possible to have an empty VHost. if ("".equals(apiConfig.getVhost())) { apiConfig.setVhost(null); } } public void readConfig(ObjectMapper mapper, API baseConfig, File stageConfigFile, String stage) { try { // If the baseConfig doesn't have a valid organization, the stage config must boolean validateOrganization = baseConfig.getOrganization() == null; ObjectReader updater = mapper.readerForUpdating(baseConfig).withAttribute(VALIDATE_ORGANIZATION, validateOrganization); // Organization must be valid in staged configuration apiConfig = updater.withAttribute(VALIDATE_ORGANIZATION, true).readValue(Utils.substituteVariables(stageConfigFile)); LOG.info("Loaded stage API-Config from file: {}", stageConfigFile); } catch (IOException e) { LOG.warn("No config file found for stage: {}", stage); apiConfig = baseConfig; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy