org.owasp.benchmark.score.report.ScatterVulns.java Source code

Java tutorial

Introduction

Here is the source code for org.owasp.benchmark.score.report.ScatterVulns.java

Source

/**
 * OWASP Benchmark Project
 *
 * This file is part of the Open Web Application Security Project (OWASP)
 * Benchmark Project For details, please see
 * <a href="https://www.owasp.org/index.php/Benchmark">https://www.owasp.org/index.php/Benchmark</a>.
 *
 * The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation, version 2.
 *
 * The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details
 *
 * @author Dave Wichers <a href="https://www.aspectsecurity.com">Aspect Security</a>
 * @created 2015
 */

package org.owasp.benchmark.score.report;

import java.awt.Color;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JFrame;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.TextAnchor;
import org.owasp.benchmark.score.BenchmarkScore;
import org.owasp.benchmark.score.parsers.OverallResult;
import org.owasp.benchmark.score.parsers.OverallResults;
import org.owasp.benchmark.score.parsers.TestResults;

public class ScatterVulns extends ScatterPlot {
    char averageLabel;
    double afr = 0;
    double atr = 0;

    // Commercial Scores
    private int commercialToolCount = 0;
    private double commercialLow = 100;
    private TestResults.ToolType commercialLowToolType = null;
    private double commercialHigh = 0;
    private TestResults.ToolType commercialHighToolType = null;
    private double commercialAve = 0;
    public final String category;
    public final String focus;

    /**
     * This calculates how all the tools did against the Benchmark in this
     * vulnerability category
     * 
     * @param title
     *            - The title of the chart to be produced.
     * @param height
     *            - Height of the chart (typically 800)
     * @param category
     *            - The vulnerability category this chart is being generated for.
     * @param toolResults
     *            - A list of each individual tool's results.
     * @param focus
     *            - A tool to emphasize in the chart.
     */
    public ScatterVulns(String title, int height, String category, Set<Report> toolResults, String focus) {
        this.category = category;
        this.focus = focus;
        display("          " + title, height, category, toolResults);
    }

    private JFreeChart display(String title, int height, String category, Set<Report> toolResults) {
        JFrame f = new JFrame(title);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // averages
        ArrayList<Double> averageFalseRates = new ArrayList<Double>();
        ArrayList<Double> averageTrueRates = new ArrayList<Double>();

        int commercialToolCount = 0;
        XYSeriesCollection dataset = new XYSeriesCollection();
        XYSeries series = new XYSeries("Scores");

        for (Report toolReport : toolResults) {
            if (!toolReport.isCommercial()) {
                OverallResult overallResult = toolReport.getOverallResults().getResults(category);
                series.add(overallResult.falsePositiveRate * 100, overallResult.truePositiveRate * 100);
            }
        }

        for (Report toolReport : toolResults) {
            if (toolReport.isCommercial()) {
                OverallResult overallResult = toolReport.getOverallResults().getResults(category);
                if (!BenchmarkScore.showAveOnlyMode) {
                    series.add(overallResult.falsePositiveRate * 100, overallResult.truePositiveRate * 100);
                }
                commercialToolCount++;
                averageFalseRates.add(overallResult.falsePositiveRate);
                averageTrueRates.add(overallResult.truePositiveRate);
            }
        }

        for (double d : averageFalseRates) {
            afr += d;
        }
        afr = afr / averageFalseRates.size();

        for (double d : averageTrueRates) {
            atr += d;
        }
        atr = atr / averageTrueRates.size();

        if (commercialToolCount > 1 || (BenchmarkScore.showAveOnlyMode && commercialToolCount == 1)) {
            series.add(afr * 100, atr * 100);
        }

        dataset.addSeries(series);

        chart = ChartFactory.createScatterPlot(title, "False Positive Rate", "True Positive Rate", dataset,
                PlotOrientation.VERTICAL, true, true, false);
        theme.apply(chart);

        XYPlot xyplot = chart.getXYPlot();

        initializePlot(xyplot);

        makeDataLabels(category, toolResults, xyplot);
        makeLegend(category, toolResults, 103, 100.5, dataset, xyplot);

        for (XYDataItem item : (List<XYDataItem>) series.getItems()) {
            double x = item.getX().doubleValue();
            double y = item.getY().doubleValue();
            double z = (x + y) / 2;
            XYLineAnnotation score = new XYLineAnnotation(x, y, z, z, dashed, Color.blue);
            xyplot.addAnnotation(score);
        }

        ChartPanel cp = new ChartPanel(chart, height, height, 400, 400, 1200, 1200, false, false, false, false,
                false, false);
        f.add(cp);
        f.pack();
        f.setLocationRelativeTo(null);
        // f.setVisible(true);

        return chart;
    }

    private void makeDataLabels(String category, Set<Report> toolResults, XYPlot xyplot) {
        HashMap<Point2D, String> map = makePointList(category, toolResults);
        for (Entry<Point2D, String> e : map.entrySet()) {
            if (e.getValue() != null) {
                Point2D p = e.getKey();
                String label = sort(e.getValue());
                XYTextAnnotation annotation = new XYTextAnnotation(label, p.getX(), p.getY());
                annotation.setTextAnchor(p.getX() < 3 ? TextAnchor.TOP_LEFT : TextAnchor.TOP_CENTER);
                annotation.setBackgroundPaint(Color.white);
                if (label.toCharArray()[0] == averageLabel) {
                    annotation.setPaint(Color.magenta);
                } else {
                    annotation.setPaint(Color.blue);
                }
                annotation.setFont(theme.getRegularFont());
                xyplot.addAnnotation(annotation);
            }
        }
    }

    private String sort(String value) {
        String[] parts = value.split(",");
        Arrays.sort(parts);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            sb.append(parts[i]);
            if (i < parts.length - 1)
                sb.append(",");
        }
        return sb.toString();
    }

    private SecureRandom sr = new SecureRandom();

    private HashMap<Point2D, String> makePointList(String category, Set<Report> toolResults) {
        HashMap<Point2D, String> map = new HashMap<Point2D, String>();
        char ch = ScatterHome.INITIAL_LABEL;

        // make a list of all points. Add in a tiny random to prevent exact
        // duplicate coordinates in map
        int commercialToolCount = 0;

        for (Report r : toolResults) {
            if (!r.isCommercial()) {
                OverallResult or = r.getOverallResults().getResults(category);
                // this puts the label just below the point
                double x = or.falsePositiveRate * 100 + sr.nextDouble() * .000001;
                double y = or.truePositiveRate * 100 + sr.nextDouble() * .000001 - 1;
                Point2D p = new Point2D.Double(x, y);
                String label = "" + ch;
                map.put(p, label);
                // Weak hack if there are more than 26 tools scored. This will only get us to 52.
                if (ch == 'Z')
                    ch = 'a';
                else
                    ch++;
            }
        }

        for (Report r : toolResults) {
            if (r.isCommercial()) {
                commercialToolCount++;
                if (!BenchmarkScore.showAveOnlyMode) {
                    OverallResult or = r.getOverallResults().getResults(category);
                    // this puts the label just below the point
                    double x = or.falsePositiveRate * 100 + sr.nextDouble() * .000001;
                    double y = or.truePositiveRate * 100 + sr.nextDouble() * .000001 - 1;
                    Point2D p = new Point2D.Double(x, y);
                    String label = "" + ch;
                    map.put(p, label);
                    // Weak hack if there are more than 26 tools scored. This will only get us to 52.
                    if (ch == 'Z')
                        ch = 'a';
                    else
                        ch++;
                }
            }
        }

        if (commercialToolCount > 1 || (BenchmarkScore.showAveOnlyMode && commercialToolCount == 1)) {
            Point2D ap = new Point2D.Double(afr * 100 + sr.nextDouble() * .000001,
                    atr * 100 + sr.nextDouble() * .000001 - 1);
            averageLabel = ch;
            map.put(ap, "" + ch);
        }
        dedupify(map);
        return map;
    }

    private void dedupify(HashMap<Point2D, String> map) {
        for (Entry<Point2D, String> e1 : map.entrySet()) {
            Entry<Point2D, String> e2 = getMatch(map, e1);
            while (e2 != null) {
                StringBuilder label = new StringBuilder();
                if (e1.getValue() != null)
                    label.append(e1.getValue());
                if (e1.getValue() != null && e2.getValue() != null)
                    label.append(",");
                if (e2.getValue() != null)
                    label.append(e2.getValue());
                e1.setValue(label.toString());
                e2.setValue(null);
                e2 = getMatch(map, e1);
            }
        }
    }

    private Entry<Point2D, String> getMatch(HashMap<Point2D, String> map, Entry<Point2D, String> e1) {
        for (Entry<Point2D, String> e2 : map.entrySet()) {
            Double xd = Math.abs(e1.getKey().getX() - e2.getKey().getX());
            Double yd = Math.abs(e1.getKey().getY() - e2.getKey().getY());
            boolean close = xd < 1 && yd < 3;
            if (e1 != e2 && e1.getValue() != null && e2.getValue() != null && close) {
                return e2;
            }
        }
        return null;
    }

    private void makeLegend(String category, Set<Report> toolResults, double x, double y,
            XYSeriesCollection dataset, XYPlot xyplot) {

        char ch = ScatterHome.INITIAL_LABEL;
        int i = -2;

        // non-commercial results
        boolean printedNonCommercialLabel = false;

        for (Report r : toolResults) {
            if (!r.isCommercial()) {
                // print non commercial label if there is at least one
                // non-commercial tool
                if (!printedNonCommercialLabel) {
                    XYTextAnnotation stroketext1 = new XYTextAnnotation("Non-Commercial", x, y + i * -3.3);
                    stroketext1.setTextAnchor(TextAnchor.CENTER_LEFT);
                    stroketext1.setBackgroundPaint(Color.white);
                    stroketext1.setPaint(Color.black);
                    stroketext1.setFont(theme.getRegularFont());
                    xyplot.addAnnotation(stroketext1);
                    i++;
                    printedNonCommercialLabel = true;
                }

                OverallResults or = r.getOverallResults();
                // Special hack to make it line up better if the letter is an 'I' or 'i'
                String label = (ch == 'I' || ch == 'i' ? ch + ":  " : "" + ch + ": ");
                double score = or.getResults(category).score * 100;
                String msg = "\u25A0 " + label + r.getToolNameAndVersion() + " (" + Math.round(score) + "%)";
                XYTextAnnotation stroketext3 = new XYTextAnnotation(msg, x, y + i * -3.3);
                stroketext3.setTextAnchor(TextAnchor.CENTER_LEFT);
                stroketext3.setBackgroundPaint(Color.white);
                stroketext3.setPaint(
                        r.getToolName().replace(' ', '_').equalsIgnoreCase(focus) ? Color.green : Color.blue);
                stroketext3.setFont(theme.getRegularFont());
                xyplot.addAnnotation(stroketext3);
                i++;
                // Weak hack if there are more than 26 tools scored. This will only get us to 52.
                if (ch == 'Z')
                    ch = 'a';
                else
                    ch++;
            }
        }

        // commercial tools
        boolean printedCommercialLabel = false;
        double commercialTotal = 0;

        for (Report r : toolResults) {
            OverallResults or = r.getOverallResults();
            if (r.isCommercial()) {

                // print commercial label if there is at least one commercial
                // tool
                if (!printedCommercialLabel) {
                    XYTextAnnotation stroketext4 = new XYTextAnnotation("Commercial", x, y + i * -3.3);
                    stroketext4.setTextAnchor(TextAnchor.CENTER_LEFT);
                    stroketext4.setBackgroundPaint(Color.white);
                    stroketext4.setPaint(Color.black);
                    stroketext4.setFont(theme.getRegularFont());
                    xyplot.addAnnotation(stroketext4);
                    i++;
                    printedCommercialLabel = true;
                }

                commercialToolCount++;
                double score = or.getResults(category).score * 100;
                // don't show the commercial tool results if in 'show ave only mode'
                if (!BenchmarkScore.showAveOnlyMode) {
                    // Special hack to make it line up better if the letter is an 'I' or 'i'
                    String label = (ch == 'I' || ch == 'i' ? ch + ":  " : "" + ch + ": ");
                    String msg = "\u25A0 " + label + r.getToolNameAndVersion() + " (" + Math.round(score) + "%)";
                    XYTextAnnotation stroketext4 = new XYTextAnnotation(msg, x, y + i * -3.3);
                    stroketext4.setTextAnchor(TextAnchor.CENTER_LEFT);
                    stroketext4.setBackgroundPaint(Color.white);
                    stroketext4.setPaint(Color.blue);
                    stroketext4.setFont(theme.getRegularFont());
                    xyplot.addAnnotation(stroketext4);
                    i++; // increment the location of the label
                    // Weak hack if there are more than 26 tools scored. This will only get us to 52.
                    if (ch == 'Z')
                        ch = 'a';
                    else
                        ch++;
                }
                commercialTotal += score;

                if (score < commercialLow) {
                    commercialLow = score;
                    commercialLowToolType = r.getToolType();
                }
                if (score > commercialHigh) {
                    commercialHigh = score;
                    commercialHighToolType = r.getToolType();
                }
            }

            // Add color emphasis to the tool of focus
            if (r.getToolName().replace(' ', '_').equalsIgnoreCase(focus)) {
                OverallResult orc = r.getOverallResults().getResults(category);
                Point2D focusPoint = new Point2D.Double(orc.falsePositiveRate * 100, orc.truePositiveRate * 100);
                Color green = new Color(0, 1, 0, 0.5f);
                makePoint(xyplot, focusPoint, 3, green);
            }
        }

        // commercial average
        if (commercialToolCount > 1 || (BenchmarkScore.showAveOnlyMode && commercialToolCount == 1)) {
            commercialAve = commercialTotal / commercialToolCount;
            XYTextAnnotation stroketext2 = new XYTextAnnotation(
                    "\u25A0 " + ch + ": Commercial Average" + " (" + Math.round(commercialAve) + "%)", x,
                    y + i * -3.3);
            stroketext2.setTextAnchor(TextAnchor.CENTER_LEFT);
            stroketext2.setBackgroundPaint(Color.white);
            stroketext2.setPaint(Color.magenta);
            stroketext2.setFont(theme.getRegularFont());
            xyplot.addAnnotation(stroketext2);

            Point2D averagePoint = new Point2D.Double(afr * 100, atr * 100);
            Color red = new Color(1, 0, 0, 0.5f);
            makePoint(xyplot, averagePoint, 3, red);
        }
    }

    public static ScatterVulns generateComparisonChart(String category, Set<Report> toolResults, String focus) {
        try {
            String scatterTitle = "OWASP Benchmark"
                    + (BenchmarkScore.mixedMode ? " -" : " v" + BenchmarkScore.benchmarkVersion) + " " + category
                    + " Comparison";
            ScatterVulns scatter = new ScatterVulns(scatterTitle, 800, category, toolResults, focus);
            scatter.writeChartToFile(new File("scorecard/Benchmark_v" + BenchmarkScore.benchmarkVersion
                    + "_Scorecard_for_" + category.replace(' ', '_') + ".png"), 800);
            return scatter;
        } catch (IOException e) {
            System.out.println("Couldn't generate Benchmark vulnerability chart for some reason.");
            e.printStackTrace();
            return null;
        }
    }

    // FIXME -- this is all a terrible mixing of view and model
    // This should be calculated and accessed through the Results (which needs a refactor to be a better DB)
    public int getCommercialToolCount() {
        return commercialToolCount;
    }

    public int getCommercialLow() {
        return (int) Math.round(commercialLow);
    }

    public TestResults.ToolType getCommercialLowToolType() {
        return commercialLowToolType;
    }

    public int getCommercialAve() {
        return (int) Math.round(commercialAve);
    }

    public int getCommercialHigh() {
        return (int) Math.round(commercialHigh);
    }

    public TestResults.ToolType getCommercialHighToolType() {
        return commercialHighToolType;
    }
}