org.zaproxy.zap.extension.openapi.ExtensionOpenApi.java Source code

Java tutorial

Introduction

Here is the source code for org.zaproxy.zap.extension.openapi.ExtensionOpenApi.java

Source

/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2017 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.openapi;

import io.swagger.models.Scheme;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import org.apache.commons.httpclient.URI;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.CommandLine;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.CommandLineArgument;
import org.parosproxy.paros.extension.CommandLineListener;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.extension.openapi.converter.swagger.SwaggerConverter;
import org.zaproxy.zap.extension.openapi.network.Requestor;
import org.zaproxy.zap.extension.spider.ExtensionSpider;
import org.zaproxy.zap.model.ValueGenerator;
import org.zaproxy.zap.spider.parser.SpiderParser;
import org.zaproxy.zap.view.ZapMenuItem;

public class ExtensionOpenApi extends ExtensionAdaptor implements CommandLineListener {

    public static final String NAME = "ExtensionOpenApi";

    private static final String THREAD_PREFIX = "ZAP-Import-OpenAPI-";

    private ZapMenuItem menuImportLocalOpenApi = null;
    private ZapMenuItem menuImportUrlOpenApi = null;
    private int threadId = 1;
    private SpiderParser customSpider;

    private CommandLineArgument[] arguments = new CommandLineArgument[2];
    private static final int ARG_IMPORT_FILE_IDX = 0;
    private static final int ARG_IMPORT_URL_IDX = 1;

    private static final Logger LOG = Logger.getLogger(ExtensionOpenApi.class);

    public ExtensionOpenApi() {
        super(NAME);
    }

    @Override
    public void hook(ExtensionHook extensionHook) {
        super.hook(extensionHook);

        /* Custom spider is added in order to explore Open API definitions. */
        ExtensionSpider spider = (ExtensionSpider) Control.getSingleton().getExtensionLoader()
                .getExtension(ExtensionSpider.NAME);
        customSpider = new OpenApiSpider();
        if (spider != null) {
            spider.addCustomParser(customSpider);
            LOG.debug("Added custom Open API spider.");
        } else {
            LOG.warn("Custom Open API spider could not be added.");
        }

        if (getView() != null) {
            extensionHook.getHookMenu().addImportMenuItem(getMenuImportLocalOpenApi());
            extensionHook.getHookMenu().addImportMenuItem(getMenuImportUrlOpenApi());
        }

        extensionHook.addApiImplementor(new OpenApiAPI(this));
        extensionHook.addCommandLine(getCommandLineArguments());
    }

    @Override
    public void unload() {
        super.unload();
        ExtensionSpider spider = (ExtensionSpider) Control.getSingleton().getExtensionLoader()
                .getExtension(ExtensionSpider.NAME);
        if (spider != null) {
            spider.removeCustomParser(customSpider);
            LOG.debug("Removed custom Open API spider.");
        }
    }

    /* Menu option to import a local OpenApi file. */
    private ZapMenuItem getMenuImportLocalOpenApi() {
        if (menuImportLocalOpenApi == null) {
            menuImportLocalOpenApi = new ZapMenuItem("openapi.topmenu.import.importopenapi");
            menuImportLocalOpenApi
                    .setToolTipText(Constant.messages.getString("openapi.topmenu.import.importopenapi.tooltip"));

            menuImportLocalOpenApi.addActionListener(new java.awt.event.ActionListener() {

                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    // Prompt for a OpenApi file.
                    final JFileChooser chooser = new JFileChooser(
                            Model.getSingleton().getOptionsParam().getUserDirectory());
                    int rc = chooser.showOpenDialog(View.getSingleton().getMainFrame());
                    if (rc == JFileChooser.APPROVE_OPTION) {
                        Model.getSingleton().getOptionsParam().setUserDirectory(chooser.getCurrentDirectory());
                        importOpenApiDefinition(chooser.getSelectedFile(), true);
                    }
                }
            });
        }
        return menuImportLocalOpenApi;
    }

    /* Menu option to import a OpenApi file from a given URL. */
    private ZapMenuItem getMenuImportUrlOpenApi() {
        if (menuImportUrlOpenApi == null) {
            menuImportUrlOpenApi = new ZapMenuItem("openapi.topmenu.import.importremoteopenapi");
            menuImportUrlOpenApi.setToolTipText(
                    Constant.messages.getString("openapi.topmenu.import.importremoteopenapi.tooltip"));

            final ExtensionOpenApi shadowCopy = this;
            menuImportUrlOpenApi.addActionListener(new java.awt.event.ActionListener() {

                @Override
                public void actionPerformed(java.awt.event.ActionEvent e) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            new ImportFromUrlDialog(View.getSingleton().getMainFrame(), shadowCopy);
                        }
                    });
                }
            });
        }
        return menuImportUrlOpenApi;
    }

    public void importOpenApiDefinition(final URI uri) {
        this.importOpenApiDefinition(uri, null, false);
    }

    public List<String> importOpenApiDefinition(final URI uri, final String siteOverride, boolean initViaUi) {
        Requestor requestor = new Requestor(HttpSender.MANUAL_REQUEST_INITIATOR);
        requestor.addListener(new HistoryPersister());
        try {
            return importOpenApiDefinition(Scheme.forValue(uri.getScheme().toLowerCase()), uri.getAuthority(),
                    requestor.getResponseBody(uri), siteOverride, initViaUi);
        } catch (IOException e) {
            if (initViaUi) {
                View.getSingleton().showWarningDialog(Constant.messages.getString("openapi.io.error"));
            }
            LOG.warn(e.getMessage(), e);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return null;
    }

    public void importOpenApiDefinition(final File file) {
        this.importOpenApiDefinition(file, false);
    }

    public List<String> importOpenApiDefinition(final File file, boolean initViaUi) {
        try {
            return importOpenApiDefinition(null, null, FileUtils.readFileToString(file, "UTF-8"), null, initViaUi);
        } catch (IOException e) {
            if (initViaUi) {
                View.getSingleton().showWarningDialog(Constant.messages.getString("openapi.io.error"));
            }
            LOG.warn(e.getMessage(), e);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
        return null;
    }

    private List<String> importOpenApiDefinition(final Scheme defaultScheme, final String defaultHost,
            final String defn, final String hostOverride, final boolean initViaUi) {
        final List<String> errors = new ArrayList<String>();
        Thread t = new Thread(THREAD_PREFIX + threadId++) {

            @Override
            public void run() {
                try {
                    Requestor requestor = new Requestor(HttpSender.MANUAL_REQUEST_INITIATOR);
                    requestor.setSiteOverride(hostOverride);
                    requestor.addListener(new HistoryPersister());
                    SwaggerConverter converter = new SwaggerConverter(defaultScheme,
                            StringUtils.isNotEmpty(hostOverride) ? hostOverride : defaultHost, defn,
                            getValueGenerator());
                    errors.addAll(requestor.run(converter.getRequestModels()));
                    // Needs to be called after converter.getRequestModels() to get loop
                    // errors
                    errors.addAll(converter.getErrorMessages());
                    if (errors.size() > 0) {
                        logErrors(errors, initViaUi);
                        if (initViaUi) {
                            View.getSingleton()
                                    .showWarningDialog(Constant.messages.getString("openapi.parse.warn"));
                        }
                    } else {
                        if (initViaUi) {
                            View.getSingleton().showMessageDialog(Constant.messages.getString("openapi.parse.ok"));
                        }
                    }
                } catch (Exception e) {
                    if (initViaUi) {
                        String exMsg = e.getLocalizedMessage();
                        if (exMsg != null) {
                            exMsg = exMsg.length() >= 125 ? exMsg.substring(0, 122) + "..." : exMsg;
                        } else {
                            exMsg = "";
                        }
                        View.getSingleton()
                                .showWarningDialog(Constant.messages.getString("openapi.parse.error", exMsg)
                                        + "\n\n" + Constant.messages.getString("openapi.parse.trailer"));
                    }
                    logErrors(errors, initViaUi);
                    LOG.warn(e.getMessage(), e);
                }
            }
        };
        t.start();

        if (!initViaUi) {
            try {
                t.join();
            } catch (InterruptedException e) {
                LOG.debug(e.getMessage(), e);
            }
            return errors;
        }
        return null;
    }

    private ValueGenerator getValueGenerator() {
        // Always get the latest ValueGenerator as it could have changed
        ExtensionSpider spider = Control.getSingleton().getExtensionLoader().getExtension(ExtensionSpider.class);
        if (spider != null) {
            return spider.getValueGenerator();
        }
        return null;
    }

    private void logErrors(List<String> errors, boolean initViaUi) {
        if (errors != null) {
            for (String error : errors) {
                if (initViaUi) {
                    View.getSingleton().getOutputPanel().append(error + "\n");
                } else {
                    LOG.warn(error);
                }
            }
        }
    }

    @Override
    public boolean canUnload() {
        return true;
    }

    @Override
    public String getAuthor() {
        return Constant.ZAP_TEAM + " plus Joanna Bona, Artur Grzesica, Michal Materniak and Marcin Spiewak";
    }

    @Override
    public String getDescription() {
        return Constant.messages.getString("openapi.desc");
    }

    @Override
    public URL getURL() {
        try {
            return new URL(Constant.ZAP_HOMEPAGE);
        } catch (MalformedURLException e) {
            return null;
        }
    }

    private CommandLineArgument[] getCommandLineArguments() {
        arguments[ARG_IMPORT_FILE_IDX] = new CommandLineArgument("-openapifile", 1, null, "",
                "-openapifile <path>      " + Constant.messages.getString("openapi.cmdline.file.help"));
        arguments[ARG_IMPORT_URL_IDX] = new CommandLineArgument("-openapiurl", 1, null, "",
                "-openapiurl <url>        " + Constant.messages.getString("openapi.cmdline.url.help"));
        return arguments;
    }

    @Override
    public void execute(CommandLineArgument[] args) {
        if (arguments[ARG_IMPORT_FILE_IDX].isEnabled()) {
            for (String file : args[ARG_IMPORT_FILE_IDX].getArguments()) {
                File f = new File(file);
                if (f.canRead()) {
                    List<String> errors = this.importOpenApiDefinition(f, false);
                    if (errors.size() > 0) {
                        for (String error : errors) {
                            CommandLine.error("Error importing definition: " + error);
                        }
                    }
                } else {
                    CommandLine.error("Cannot read Open API file: " + f.getAbsolutePath());
                }
            }
        }
        if (arguments[ARG_IMPORT_URL_IDX].isEnabled()) {
            for (String urlstr : args[ARG_IMPORT_URL_IDX].getArguments()) {
                try {
                    URI url = new URI(urlstr, false);
                    List<String> errors = this.importOpenApiDefinition(url, null, false);
                    if (errors.size() > 0) {
                        for (String error : errors) {
                            CommandLine.error("Error importing definition: " + error);
                        }
                    }
                } catch (Exception e) {
                    CommandLine.error(e.getMessage(), e);
                }
            }
        }
    }

    @Override
    public List<String> getHandledExtensions() {
        return null;
    }

    @Override
    public boolean handleFile(File file) {
        // Not supported
        return false;
    }
}