{% extends base.html %}

{% block title %}Target Manager{% end %}

{% block includes %}
<script type="text/javascript" charset="utf-8" src="{{ static_url('js/jquery.dataTables.min.js') }}"></script>
<script type="text/javascript" charset="utf-8" src="{{ static_url('js/dataTables.bootstrap.js') }}"></script>
<script type="text/javascript" charset="utf-8" src="{{ static_url('js/filesaver.min.js') }}"></script>
<link href="{{ static_url('css/dataTables.bootstrap.css') }}" rel="stylesheet">
{% end %}

{% block content %}
<div class="row">
    <!-- Input area to add new targets -->
    <div class="col-xs-12 col-sm-12 col-md-6 col-lg-6">
        <div class="row-fluid">
            <h3>Add Targets</h3>
        </div>
        <div class="row-fluid">
            <textarea id="newTargetUrls" type="text" class="form-control" placeholder="Targets seperated by new line" autofocus="" style="max-width:100%"></textarea>
        </div>
    <div class="row-fluid" align="left" style="margin-top:10px;">
          <button class="btn btn-primary" id="addTargetBtn" type="button" onclick="newTargets($('#newTargetUrls').val(), this);">Add Targets</button>
    </div>
    </div>
    <!-- End Input area for new targets -->
    <!-- Target list -->
    <div class="col-xs-12 col-sm-12 col-md-6 col-lg6">
        <div class="row">
            <div class="col-md-6">
                <div class="input-group">
                    <div class="input-group">
                      <input type="text" id="sessionName" class="form-control" value="Session" readonly>
                      <input type="hidden" id="sessionId" class="form-control" value="Session">
                      <span class="input-group-btn">
                        <button class="btn btn-primary" data-toggle="modal" data-target="#sessionModal" type="button">
                            <i class="fa fa-flag-checkered"></i>
                        </button>
                      </span>
                    </div><!-- /input-group -->
                </div>
            </div>
            <div class="col-md-6 btn-group">
                <button type="button" class="btn btn-default" download="targets.txt" id="exportTargetsBtn" onclick="exportTargets();">
                    <i class="fa fa-list"></i> Export
                </button>
                <button type="button" class="btn btn-success" data-toggle="modal" data-target="#pluginLaunchModal">
                    <i class="fa fa-flash"></i> Run
                </button>
            </div>
        </div>
        <br>
        <div class="row-fluid">
            <div class="col-md-12">
                <table id='targetTable' class="table table-striped table-condensed dt-responsive">
                    <thead>
                        <tr>
                            <th><input type="checkbox" id="targetTableSelectAllCheckbox" onclick="toggleAll(this);"/></th>
                            <th class="textSearch">Target</th>
                            <th>Severity</th>
                            <th>Actions</th>
                        </tr>
                        <tr style="display:none;">
                            <th colspan="3" style="text-align:center;"><span class="selected-target-count">0</span> targets currently selected.</th>
                            <th>
                                <span class="btn-group" state="bulk">
                                    <button action="bulk-remove" class="btn btn-xs btn-warning" data-toggle="tooltip" data-placement="bottom" title="Remove selected targets from this session"><i class="fa fa-minus"></i></button>
                                    <button action="bulk-delete" class="btn btn-xs btn-danger" data-toggle="tooltip" data-placement="bottom" title="Delete selected targets from everywhere"><i class="fa fa-times"></i></button>
                                </span>
                            </th>
                        </tr>
                    </thead>
                    <tbody class="searchable">
                      <div id="severity_list">
                        <h5><strong>Filter by Severity:</strong></h5>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="passing">Passing</label>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="info">Info</label>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="low">Low</label>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="medium">Medium</label>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="high">High</label>
                        <label class="checkbox-inline"><input type="checkbox" name="severity" value="critical">Critical</label>
                      </div>
                      <br>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    <!-- End Target list -->
</div>

{% module Template("plugin_launcher.html",
    plugin_launch_modal_id="pluginLaunchModal",
    plugins_api_url="mySpace.plugins_api_url",
    worklist_post_func="postToWorkList") %}

<!-- Small modal -->
<div class="modal fade" id="sessionModal" tabindex="-1" role="dialog" aria-labelledby="sessionModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <div class="input-group">
                <input id="newSessionName" type="text" class="form-control" placeholder="New Session">
                <span class="input-group-btn">
                    <button class="btn btn-primary" onclick="newSession($('#newSessionName').val());" type="button">Add!</button>
                </span>
            </div><!-- /input-group -->
        </div>
        <div class="modal-body">
            <table id='sessionTable' class="table table-striped table-condensed dt-responsive">
                <thead>
                    <tr>
                        <td></td>
                        <td>Session</td>
                        <td></td>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </div>
    </div>
  </div>
</div>

<script>
var mySpace = {
                    owtf_sessions_api_url:"{{ owtf_sessions_api_url }}",
                    targets_api_url:"{{ targets_api_url }}",
                    targets_search_api_url:"{{ targets_search_api_url }}",
                    targets_ui_url:"{{ targets_ui_url }}",
                    plugins_api_url:"{{ plugins_api_url }}?group=web&group=network",
                    worklist_api_url:"{{ worklist_api_url }}"
                  };

var checked_rows = []; // Track checked rows for bulk operations
function updateSelectionCount() {
    $('#targetTable .selected-target-count').closest('tr').css('display', checked_rows.length ? 'table-row' : 'none');
    $('#targetTable .selected-target-count').html(checked_rows.length);
}

// Ensure an element is (state=true) or is not (state=false) in array
function setArrayElement(array, element, state) {
    var index = array.indexOf(element);
    if (index === -1 && state) {
        array.push(element);
    } else if (index !== -1 && !state) {
        array.splice(index,1);
    }
}

// Toggle all visible checkboxes when select all is clicked
function toggleAll(checkbox) {
    var table = $(checkbox).closest('table');
    $visible_checkboxes = $('td input:checkbox',table).filter(':visible');
    $visible_checkboxes.prop('checked',checkbox.checked);
    $visible_checkboxes.each(function() {
        setArrayElement(checked_rows, parseInt($(this).attr('target-id')), checkbox.checked);
    });
    updateSelectionCount();
}

function getSelectedTargets() {
    return checked_rows;
}

// Main function that posts using API to launch plugins
function postToWorkList(selectedPluginData, force_overwrite) {
    selectedPluginData["id"] = getSelectedTargets();
    selectedPluginData["force_overwrite"] = force_overwrite;
    if (selectedPluginData["id"].length < 1) {  // If no targets selected
        alertFail("No targets selected to launch plugins");
    }
    $.ajax({
            url:mySpace.worklist_api_url,
            type:'POST',
            data:$.param(selectedPluginData, true),
            success:pluginLaunchSuccess,
            error:function(xhr, textStatus, serverResponse) {
                alertFail("Server replied: "+serverResponse);
                }
            });
}

// Close the modal when plugins are launched successfully
function pluginLaunchSuccess() {
    $('#pluginLaunchModal').modal('hide');
    alertSuccess("Selected plugins launched, please check worklist to manage :D");
}

function removeTargetFromCurrentSessionRequest(target_id, success_callback, error_callback) {
    var session_id = $('#sessionId').val();
    $.ajax({url:mySpace.owtf_sessions_api_url+session_id+'/remove',
        type:'PATCH',
        data:{'target_id':target_id},
        success: function() {
            var checked_index = checked_rows.indexOf(target_id);
            if(checked_index > -1) {
                checked_rows.splice(checked_index, 1);
            }
            success_callback.apply(this, arguments);
        },
        error: error_callback
    });
}

// Remove target from current session
function removeTargetFromCurrentSession(target_id) {
    removeTargetFromCurrentSessionRequest(target_id, updateCurrentSessionInfo, function(xhr, textStatus, serverResponse) {
        alertFail("Server replied: "+serverResponse);
    });
}

function removeTargetsFromCurrentSession(target_ids) {
    var target_count = target_ids.length;
    var status = {
        complete:[],
        failed:[]
    };
    while(checked_rows.length) {
        var id = checked_rows.pop();
        $('td input[target-id='+id+']', $('#targetTable')).closest('tr').addClass('processing');
        removeTargetFromCurrentSessionRequest(id, function() {
            status.complete.push(id);
            summarise();
        }, function(xhr, textStatus, serverResponse) {
            status.failed.push({id: id, response: serverResponse});
            summarise();
        });
    }
    function summarise() {
        if(status.complete.length + status.failed.length == target_count) {
            var base_message = status.complete.length + " targets removed."
            if(status.failed.length) {
                alertWarning(base_message + status.failed.length + " attempts failed.");
            } else {
                alertSuccess(base_message);
            }
            updateCurrentSessionInfo();
        }
    }
}

// New session
function newSession(session_name) {
    if (session_name) {
        session_name = session_name.substring(0, 50);
        $.ajax({
                url:mySpace.owtf_sessions_api_url,
                type:'POST',
                data:{name:session_name},
                success: function() { updateSessions(); updateCurrentSessionInfo();},
                error:function(xhr, textStatus, serverResponse) {
                    alertFail("Unable to add "+escapeHtml(session_name)); // sanitized session_name parameter
                    }
        });
    }
}

// Delete session
function deleteSession(session_id) {
    $.ajax({
            url:mySpace.owtf_sessions_api_url+session_id,
            type:'DELETE',
            success: function() { updateSessions(); updateCurrentSessionInfo();},
            error:function(xhr, textStatus, serverResponse) {
                alertFail("Server replied: "+serverResponse);
                }
            });
}

// Active a session
function activateSession(session_id) {
    $.ajax({url:mySpace.owtf_sessions_api_url+session_id+'/activate',
        type:'PATCH',
        success: updateCurrentSessionInfo,
        error:function(xhr, textStatus, serverResponse) {
            alertFail("Server replied: "+serverResponse);
            }
    });
    $('#sessionModal').modal('hide');
}

// This function will be called when the session modal closes
$('#sessionModal').on('hidden.bs.modal', function () {
    var radio_session = $('input:radio[name="session_id"]');
    if (radio_session.is(':checked') > 0){
        // The session is selected. Nothing to do in this case
        return;
    }
    if(radio_session.length == 1){
        // If only one session is found in the db, then choose that by default.
        activateSession(radio_session.val());
    } else{
        // Alert the user for not selecting any session
        $("#sessionName").val("No session selected :(");
        alertFail(" No active action session found. Please select a session");
    }
});

// Function that adds targets to the target list(table infact)
function addSessions(data, status, xhr) {
    jQuery('#sessionTable').dataTable().fnClearTable();
    $.each(data, function(index, obj) {
        checked = (obj.active) ? 'checked' : '';
        actionButtons = '<a href="#" onclick="deleteSession('+obj.id+');" data-toggle="tooltip" data-placement="bottom" title="Delete session">';
        actionButtons += '<span class="btn btn-xs btn-danger"><i class="fa fa-times"></i></span></a>'
        jQuery('#sessionTable').dataTable().fnAddData([
            '<input type="radio" name="session_id" onclick="activateSession('+obj.id+');" value="'+obj.id+'" '+checked+'>',
            escapeHtml(obj.name),
            actionButtons
        ]);
    });
}

// Function to update sessions in the list
function updateSessions() {
    $.getJSON( mySpace.owtf_sessions_api_url, addSessions );
}

// get label color for target rating
function getLabel(level) {
    if (level == 0)
        return "<span class='label label-passing' style='text'>Passing</span>";
    else if (level == 1)
        return "<span class='label label-success' style='text'>Info</span>";
    else if (level == 2)
        return "<span class='label label-info' style='margin-bottom: 0px'>Low</span>";
    else if (level == 3)
        return "<span class='label label-warning' style='margin-bottom: 0px'>Medium</span>";
    else if (level == 4)
        return "<span class='label label-danger' style='margin-bottom: 0px'>High</span>";
    else if (level == 5)
        return "<span class='label label-danger' style='margin-bottom: 0px'>Critical</span>";
    return "";
}

// Function that adds targets to the target list(table infact)
function getActionButtons(obj) {
    var actionButtons = '<div class="btn-group">';
    actionButtons += '<button class="btn btn-xs btn-warning" onclick="removeTargetFromCurrentSession('+obj.id+');" data-toggle="tooltip" data-placement="bottom" title="Remove target from this session">';
    actionButtons += '<i class="fa fa-minus"></i></button>';
    actionButtons += '<button class="btn btn-xs btn-danger" onclick="deleteTarget('+obj.id+');" data-toggle="tooltip" data-placement="bottom" title="Delete target from everywhere">';
    actionButtons += '<i class="fa fa-times"></i></button>';
    actionButtons += '</div>';
    return(actionButtons);
}

function getSeverityLabel(obj) {
  if (obj.max_user_rank > obj.max_owtf_rank)
    return getLabel(obj.max_user_rank);
  return getLabel(obj.max_owtf_rank);
}

// Function used to update targets in the list, useful when new targets are added so that table can be updated
// First update session info and then targets
function updateCurrentSessionInfo() {
    $.getJSON(mySpace.owtf_sessions_api_url+'?active=true', function(data) {
        $.each(data, function(index, obj) {
            $('#sessionName').val(obj.name);
            $('#sessionId').val(obj.id);
        });
    });
    // Update targets as well
    $('#targetTable').dataTable().fnDraw(true);
    updateSelectionCount();
}

// Export all targets for the current session to a text file
function exportTargets() {
    $.ajax({
        url:'/api/targets/',
        type:'GET',
        cache: false,
        success: function(data) {
            targetsArray = [];
            $.each(data, function(index, obj) {
            targetsArray.push(obj["target_url"] + '\n');
        });
            var blob = new Blob(targetsArray, {type: "text/plain;charset=utf-8"});
            saveAs(blob, "targets.txt");
        },
        error:function(xhr, textStatus, serverResponse) {
            alertFail("Unable to export targets");
        }
});

}

function deleteTargetRequest(target_id, success_callback, error_callback) {
    $.ajax({
        url:mySpace.targets_api_url+target_id,
        type:'DELETE',
        success:function() {
            var checked_index = checked_rows.indexOf(target_id);
            if(checked_index > -1) {
                checked_rows.splice(checked_index, 1);
            }
            success_callback.apply(this, arguments);
        },
        error: error_callback
    });
}

// Delete target with id using API
function deleteTarget(target_id) {
    deleteTargetRequest(target_id, updateCurrentSessionInfo, function(xhr, textStatus, serverResponse) {
        alertFail("Server replied: "+serverResponse);
    });
}

function deleteTargets(target_ids) {
    var target_count = target_ids.length;
    var status = {
        complete:[],
        failed:[]
    };
    while(checked_rows.length) {
        var id = checked_rows.pop();
        $('td input[target-id='+id+']', $('#targetTable')).closest('tr').addClass('processing');
        deleteTargetRequest(id, function() {
            status.complete.push(id);
            summarise();
        }, function(xhr, textStatus, serverResponse) {
            status.failed.push({id: id, response: serverResponse});
            summarise();
        });
    }
    function summarise() {
        if(status.complete.length + status.failed.length == target_count) {
            var base_message = status.complete.length + " targets deleted."
            if(status.failed.length) {
                alertWarning(base_message + status.failed.length + " attempts failed.");
            } else {
                alertSuccess(base_message);
            }
            updateCurrentSessionInfo();
        }
    }
}

function isUrl(str) {
    // TODO: Add all url protocols to support network plugins
    var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-]*[\w@?^=%&amp;\/~+#-])?/;
    return urlPattern.test(str);
}

// Add new targets using API
function newTargets(targetUrlsString, button) {
    disableInput("#addTargetBtn"); // Hide the button and show once the request is complete
    var lines = targetUrlsString.split('\n');
    var targetUrls = [];

    // Check if each line is a valid url, and add protocols if only host name is provided
    $.each(lines, function(index, line) {
        if (isUrl(line)) { // Check if a valid url
            targetUrls.push(line);
            $("#newTargetUrls").val("");
        } else if (isUrl("http://"+line)) { // If not valid url and adding http:// makes it a valid url
            targetUrls.push("http://"+line);
            targetUrls.push("https://"+line);
            $("#newTargetUrls").val("");
        } else { // Not a valid url
            alertFail(escapeHtml(line)+" is not a valid url");
        }
    });

    // Since we only have valid urls now in targetUrls, add them using api
    // Proceed only if there is atleast one valid url
    if (targetUrls.length > 0) {
        for (var i=0; i < targetUrls.length; i++) {
            $.ajax({
                    url:mySpace.targets_api_url,
                    type:'POST',
                    data:{'target_url':targetUrls[i]},
                    success: function() {
                        updateCurrentSessionInfo();
                    },
                    error:function(xhr, textStatus, serverResponse) {
                        alertFail("Unable to add " + escapeHtml(unescape(this.data.split(/\=(.+)?/)[1])));
                    }
            });
        }
        alertInfo("Targets are being added in the background, and will appear in the table soon");
    }
    enableInput("#addTargetBtn"); // If the valid targetUrls length is 0 then input remains disabled forever
}

function getArray(data) {
    var table_array = [];
    $.each(data, function(index, obj) {
      table_array.push([
        '<input type="checkbox" target-id="'+obj.id+'"/>',
        '<a href="'+mySpace.targets_ui_url+obj.id+'">'+obj.target_url+' ('+obj.host_ip+') </a>',
        getSeverityLabel(obj),
        getActionButtons(obj)
      ]);
    });
    return(table_array);
}

function dummyAjax(data, callback, settings) {
  // This function takes care of converting dataTable data into owtf api
  // and then the response back to dataTables type
  var get_parameters = {};
  var draw = data.draw;
  get_parameters['limit'] = data.length;
  get_parameters['offset'] = data.start;
  if (data.search.value) {
    get_parameters['target_url'] = data.search.value;
  }

  $.getJSON(mySpace.targets_search_api_url+'?'+$.param(get_parameters, true), function (responseData) {
    // Remove the spinner once we have the response, since dataTables takes very less time to populate
    // removeOverlaySpinner('worklistTable');
    callback({
      "draw": draw,
      "recordsTotal": responseData["records_total"],
      "recordsFiltered": responseData["records_filtered"],
      "data": getArray(responseData["data"]),
      "error": null
    });
  });
}

function drawTargetTable() {
    // Initialize target table
    $('#targetTable').dataTable({
        "pagingType": "full_numbers",
        "iDisplayLength" : 100,
        "bAutoWidth": false,
        "bSort": false, // Not yet supported in API, can after shifting to postgres
        "stateSave": true,
        "bServerSide": true,
        "ajax": dummyAjax,
        "fnDrawCallback": function( settings ) {
            // Managing the "Select all" checkbox
            // everytime the table is drawn, it checks if all the
            // checkboxes are checked and if they are, then the select all
            // checkbox in the table header is selected

            //ensure 'checked_rows' are redrawn properly
            for(var i = 0; i < checked_rows.length; i++) {
                var target_id = checked_rows[i];
                $('td input[target-id='+target_id+']').prop('checked', true);
            }

            var allChecked = true;
            $('#targetTable tbody tr').each(function() {
                $(this).find(':checkbox').each(function() {
                    if (!$(this).is(':checked')) allChecked = false;
                });
            });
            $('#targetTableSelectAllCheckbox').prop('checked', allChecked);
        },
    });

    var table = $("#targetTable").dataTable();

    // Override global search of data Tables
    var search_box = $("div.dataTables_filter input[aria-controls='targetTable']");
    search_box.unbind();
    search_box.on("keyup change", function (e) {
      if (((e.type == "keyup") && (e.keyCode == 13)) || (e.type == "change")) {
        table.fnFilter(this.value);
      }
    });
    // Severity Filter Implementation
    $("input[name='severity']").change(function() {
      var count=0;
      $('.searchable tr').hide();
      $.each($("input[name='severity']:checked"), function() {
          var rex = new RegExp($(this).val(), 'i');
          $('.searchable tr').filter(function () {
              return rex.test($(this).text());
          }).show();
          count++;
      });
      if (count==0)
      {
        $('.searchable tr').show();
      }
    });

    //Update record of checked checkboxes on individual change
    $('#targetTable').on('click', 'td input:checkbox', function() {
        var target_id = $(this).attr('target-id');
        setArrayElement(checked_rows, parseInt(target_id), $(this)[0].checked);
        updateSelectionCount();
    });

    //These operations are very fast locally, spinner unnecessary
    $('#targetTable tr button[action=bulk-delete]').on('click', function() {
        deleteTargets(checked_rows);
        updateSelectionCount();
    });

    $('#targetTable tr button[action=bulk-remove]').on('click', function() {
        removeTargetsFromCurrentSession(checked_rows);
        updateSelectionCount();
    });
}

// The target list table has to be initialized first
$(document).ready(function() {
    drawTargetTable();
    // Initialize session table but donot populate until modal is opened
    $('#sessionTable').dataTable({
        "bAutoWidth": false,
        "stateSave": true,
        "columnDefs": [{
          "targets": [0],
          "orderable": false
        }]
    });
    // Bind the modal launch to populate sessions
    $('#sessionModal').on('show.bs.modal', function (e) {
        updateSessions();
    });
    updateCurrentSessionInfo();
});
</script>
{% end %}
