/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2015 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.control; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import org.apache.commons.lang.SystemUtils; import org.apache.log4j.Logger; import org.jdesktop.swingx.JXTree; import org.parosproxy.paros.Constant; import org.parosproxy.paros.view.View; /** * An utility/helper class that extract textual representations of running issues of add-ons and show warning messages of * add-ons that can not be run. * * @since 2.4.0 */ public final class AddOnRunIssuesUtils { private static final Logger LOGGER = Logger.getLogger(AddOnRunIssuesUtils.class); private AddOnRunIssuesUtils() { } /** * Shows a warning dialogue with the add-ons and its corresponding running issues or the issues if is extensions. * <p> * The dialogue is composed with the given {@code message} and a tree in which are shown the add-ons and its issues as child * nodes of the add-ons. * * @param message the main message shown in the dialogue * @param availableAddOns the add-ons that are available, used to create and check the running issues * @param addOnsNotRunnable the add-ons with running issues that will be shown in the tree */ public static void showWarningMessageAddOnsNotRunnable( String message, AddOnCollection availableAddOns, Collection<AddOn> addOnsNotRunnable) { Object[] msgs = { message, createScrollableTreeAddOnsNotRunnable( availableAddOns, addOnsNotRunnable.toArray(new AddOn[addOnsNotRunnable.size()])) }; JOptionPane.showMessageDialog( View.getSingleton().getMainFrame(), msgs, Constant.PROGRAM_NAME, JOptionPane.WARNING_MESSAGE); } /** * Creates a scrollable tree with the given add-ons as root nodes and its issues as child nodes. * * @param availableAddOns the add-ons that are available, used to create check the running issues * @param addOnsNotRunnable the add-ons with running issues that will be shown in the tree * @return the tree wrapper in a {@code JSCrollPane} */ private static JScrollPane createScrollableTreeAddOnsNotRunnable( final AddOnCollection availableAddOns, AddOn... addOnsNotRunnable) { AddOnSearcher addOnSearcher = new AddOnSearcher() { @Override public AddOn searchAddOn(String id) { return availableAddOns.getAddOn(id); } }; DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(""); for (AddOn addOn : addOnsNotRunnable) { DefaultMutableTreeNode addOnNode = new DefaultMutableTreeNode(addOn.getName()); AddOn.AddOnRunRequirements requirements = addOn.calculateRunRequirements(availableAddOns.getAddOns()); List<String> issues = getUiRunningIssues(requirements, addOnSearcher); if (issues.isEmpty()) { issues.addAll(getUiExtensionsRunningIssues(requirements, addOnSearcher)); } for (String issue : issues) { addOnNode.add(new DefaultMutableTreeNode(issue)); } rootNode.add(addOnNode); } JXTree tree = new JXTree(new DefaultTreeModel(rootNode)); tree.setVisibleRowCount(5); tree.setEditable(false); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.expandAll(); return new JScrollPane(tree); } /** * Shows a confirmation dialogue (yes-no question), with the given message at the top, followed by a tree in which is shown * the add-on with its corresponding running issues and, at the bottom before the yes and no buttons, the given question. * * @param message the main message shown in the dialogue * @param question the questions shown at the bottom of the dialogue, before the buttons * @param availableAddOns the add-ons that are available, used to create check the running issues * @param addOnNotRunnable the add-on with running issues that will be shown in the tree * @return {@code true} if it is confirmed, {@code false} otherwise */ public static boolean askConfirmationAddOnNotRunnable( String message, String question, AddOnCollection availableAddOns, AddOn addOnNotRunnable) { Object[] msgs = { message, createScrollableTreeAddOnsNotRunnable(availableAddOns, addOnNotRunnable), question }; return JOptionPane.showConfirmDialog( View.getSingleton().getMainFrame(), msgs, Constant.PROGRAM_NAME, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION; } /** * Returns the textual representations of the running issues (Java version and dependency), if any. * <p> * The messages are internationalised thus suitable for UI components. * * @param requirements the run requirements of the add-on * @param addOnSearcher the class responsible for searching add-ons with a given id, used to search for add-ons that are * missing for the add-on * @return a {@code List} containing all the running issues of the add-on, empty if none * @see #getRunningIssues(AddOn.BaseRunRequirements) * @see #getUiExtensionsRunningIssues(AddOn.AddOnRunRequirements, AddOnSearcher) */ public static List<String> getUiRunningIssues(AddOn.BaseRunRequirements requirements, AddOnSearcher addOnSearcher) { List<String> issues = new ArrayList<>(2); if (requirements.isNewerJavaVersionRequired()) { if (requirements.getAddOn() != requirements.getAddOnMinimumJavaVersion()) { issues.add(MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.javaversion.dependency"), requirements.getMinimumJavaVersion(), (SystemUtils.JAVA_VERSION == null ? Constant.messages.getString("cfu.warn.unknownJavaVersion") : SystemUtils.JAVA_VERSION), requirements.getAddOnMinimumJavaVersion().getName())); } else { issues.add(MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.javaversion"), requirements.getMinimumJavaVersion(), (SystemUtils.JAVA_VERSION == null ? Constant.messages.getString("cfu.warn.unknownJavaVersion") : SystemUtils.JAVA_VERSION))); } } if (requirements.hasDependencyIssue()) { List<Object> issueDetails = requirements.getDependencyIssueDetails(); AddOn addOn; String message = null; switch (requirements.getDependencyIssue()) { case CYCLIC: message = Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.id"); break; case OLDER_VERSION: // Do not set a message, the state is already reported as requiring an update. break; case MISSING: String addOnId = (String) issueDetails.get(0); if (addOnSearcher != null) { addOn = addOnSearcher.searchAddOn(addOnId); } else { addOn = null; } if (addOn == null) { message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.id"), addOnId); } else { message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon"), addOn.getName()); } break; case PACKAGE_VERSION_NOT_BEFORE: addOn = (AddOn) issueDetails.get(0); message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.version.notBefore"), addOn.getName(), issueDetails.get(1), Integer.valueOf(addOn.getFileVersion())); break; case PACKAGE_VERSION_NOT_FROM: addOn = (AddOn) issueDetails.get(0); message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.version.notAfter"), addOn.getName(), issueDetails.get(1), Integer.valueOf(addOn.getFileVersion())); break; case VERSION: addOn = (AddOn) issueDetails.get(0); if (addOn.getVersion() == null) { message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.semver.notAvailable"), addOn.getName(), issueDetails.get(1)); } else { message = MessageFormat.format( Constant.messages.getString("cfu.warn.addon.with.missing.requirements.addon.semver"), addOn.getName(), issueDetails.get(1), addOn.getVersion()); } break; default: message = Constant.messages.getString("cfu.warn.addon.with.missing.requirements.unknown"); LOGGER.warn("Failed to handle dependency issue with name \"" + requirements.getDependencyIssue().name() + "\" and details: " + issueDetails); break; } if (message != null) { issues.add(message); } } return issues; } /** * Returns the textual representations of the running issues (Java version and dependency) of the extensions of hte add-on, * if any. * <p> * The messages are internationalised thus suitable for UI components. * * @param requirements the run requirements of the add-on, whose extensions' run requirements will be used * @param addOnSearcher the class responsible for searching add-ons with a given id, used to search for add-ons that are * missing for the add-on * @return a {@code List} containing all the running issues of the add-on, empty if none * @see #getRunningIssues(AddOn.BaseRunRequirements) * @see #getUiExtensionsRunningIssues(AddOn.AddOnRunRequirements, AddOnSearcher) */ public static List<String> getUiExtensionsRunningIssues(AddOn.AddOnRunRequirements requirements, AddOnSearcher addOnSearcher) { if (!requirements.hasExtensionsWithRunningIssues()) { return new ArrayList<>(0); } List<String> issues = new ArrayList<>(10); for (AddOn.ExtensionRunRequirements extReqs : requirements.getExtensionRequirements()) { issues.addAll(getUiRunningIssues(extReqs, addOnSearcher)); } return issues; } /** * Returns the textual representations of the running issues (Java version and dependency), if any. * <p> * The messages are not internationalised, should be used only for logging and non UI uses. * * @param requirements the run requirements of the add-on or extension * @return a {@code List} containing all the running issues of the add-on or extension, empty if none * @see #getUiRunningIssues(AddOn.BaseRunRequirements, AddOnSearcher) * @see #getUiExtensionsRunningIssues(AddOn.AddOnRunRequirements, AddOnSearcher) */ public static List<String> getRunningIssues(AddOn.BaseRunRequirements requirements) { List<String> issues = new ArrayList<>(2); String issue = getJavaVersionIssue(requirements); if (issue != null) { issues.add(issue); } issue = getDependencyIssue(requirements); if (issue != null) { issues.add(issue); } return issues; } /** * Returns the textual representation of the issues that prevent the extensions of the add-on from being run, if any. * <p> * The messages are not internationalised, should be used only for logging and non UI uses. * * @param requirements the run requirements of the add-on whose extensions' run requirements will be used * @return a {@code String} representing the running issue, {@code null} if none. * @see AddOn.AddOnRunRequirements#getExtensionRequirements() */ public static List<String> getExtensionsRunningIssues(AddOn.AddOnRunRequirements requirements) { if (!requirements.hasExtensionsWithRunningIssues()) { return new ArrayList<>(0); } List<String> issues = new ArrayList<>(10); for (AddOn.ExtensionRunRequirements extReqs : requirements.getExtensionRequirements()) { issues.addAll(getRunningIssues(extReqs)); } return issues; } /** * Returns the textual representation of the Java version issue that prevents the add-on or extension from being run, if * any. * <p> * The message is not internationalised, should be used only for logging and non UI uses. * * @param requirements the run requirements of the add-on or extension * @return a {@code String} representing the running issue, {@code null} if none. */ public static String getJavaVersionIssue(AddOn.BaseRunRequirements requirements) { if (!requirements.isNewerJavaVersionRequired()) { return null; } if (requirements.getAddOn() != requirements.getAddOnMinimumJavaVersion()) { return MessageFormat.format( "Minimum Java version: {0} (\"{1}\" add-on)", requirements.getMinimumJavaVersion(), requirements.getAddOnMinimumJavaVersion().getName()); } return MessageFormat.format("Minimum Java version: {0}", requirements.getMinimumJavaVersion()); } /** * Returns the textual representation of the issue that prevents the add-on or extension from being run, if any. * <p> * The messages are not internationalised, should be used only for logging and non UI uses. * * @param requirements the run requirements of the add-on or extension * @return a {@code String} representing the running issue, {@code null} if none. */ public static String getDependencyIssue(AddOn.BaseRunRequirements requirements) { if (!requirements.hasDependencyIssue()) { return null; } List<Object> issueDetails = requirements.getDependencyIssueDetails(); switch (requirements.getDependencyIssue()) { case CYCLIC: return "Cyclic dependency with: " + issueDetails.get(0); case OLDER_VERSION: return "Older version still installed: " + issueDetails.get(0); case MISSING: String addOnId = (String) issueDetails.get(0); return MessageFormat.format("Add-On with ID \"{0}\"", addOnId); case PACKAGE_VERSION_NOT_BEFORE: AddOn addOn = (AddOn) issueDetails.get(0); return MessageFormat.format( "Add-on \"{0}\" with version not before {1} (found version {2})", addOn.getName(), issueDetails.get(1), Integer.valueOf(addOn.getFileVersion())); case PACKAGE_VERSION_NOT_FROM: addOn = (AddOn) issueDetails.get(0); return MessageFormat.format( "Add-on \"{0}\" with version not after {1} (found version {2})", addOn.getName(), issueDetails.get(1), Integer.valueOf(addOn.getFileVersion())); case VERSION: addOn = (AddOn) issueDetails.get(0); if (addOn.getVersion() == null) { return MessageFormat.format( "Add-on \"{0}\" with semantic version >= {1} (found no semantic version)", addOn.getName(), issueDetails.get(1)); } return MessageFormat.format( "Add-on \"{0}\" with semantic version >= {1} (found version {2})", addOn.getName(), issueDetails.get(1), addOn.getVersion()); default: LOGGER.warn("Failed to handle dependency issue with name \"" + requirements.getDependencyIssue().name() + "\" and details: " + issueDetails); return null; } } /** * Interface to search for add-ons with a given id. * <p> * Used to search for missing add-ons as reported by run requirements of an add-on. * * @see AddOn.BaseRunRequirements.DependencyIssue#MISSING */ public static interface AddOnSearcher { /** * Returns an add-on with the given id, if none is found returns {@code null}. * * @param id the id of the add-on * @return the add-on, or {@code null} if not found */ AddOn searchAddOn(String id); } }