Skip to content

Commit

Permalink
Implement the ability to complete a campaign. Fixes gophish#290.
Browse files Browse the repository at this point in the history
First implementation of new alert format.
  • Loading branch information
jordan-wright committed Jul 12, 2016
1 parent ca43a57 commit 1dbf061
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ static/js/moment.min.js linguist-vendored
static/js/jquery* linguist-vendored
static/js/ui-bootstrap-* linguist-vendored
static/js/datatables* linguist-vendored
static/js/sweetalert2.min.js linguist-vendored
16 changes: 16 additions & 0 deletions controllers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ func API_Campaigns_Id_Results(w http.ResponseWriter, r *http.Request) {
}
}

// API_Campaigns_Id_Complete effectively "ends" a campaign.
// Future phishing emails clicked will return a simple "404" page.
func API_Campaigns_Id_Complete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
switch {
case r.Method == "GET":
err := models.CompleteCampaign(id, ctx.Get(r, "user_id").(int64))
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error completing campaign"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Campaign completed successfully!"}, http.StatusOK)
}
}

// API_Groups returns a list of groups if requested via GET.
// If requested via POST, API_Groups creates a new group and returns a reference to it.
func API_Groups(w http.ResponseWriter, r *http.Request) {
Expand Down
13 changes: 12 additions & 1 deletion controllers/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func CreateAdminRouter() http.Handler {
api.HandleFunc("/campaigns/", Use(API_Campaigns, mid.RequireAPIKey))
api.HandleFunc("/campaigns/{id:[0-9]+}", Use(API_Campaigns_Id, mid.RequireAPIKey))
api.HandleFunc("/campaigns/{id:[0-9]+}/results", Use(API_Campaigns_Id_Results, mid.RequireAPIKey))
api.HandleFunc("/campaigns/{id:[0-9]+}/complete", Use(API_Campaigns_Id_Complete, mid.RequireAPIKey))
api.HandleFunc("/groups/", Use(API_Groups, mid.RequireAPIKey))
api.HandleFunc("/groups/{id:[0-9]+}", Use(API_Groups_Id, mid.RequireAPIKey))
api.HandleFunc("/templates/", Use(API_Templates, mid.RequireAPIKey))
Expand Down Expand Up @@ -110,6 +111,11 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
if err != nil {
Logger.Println(err)
}
// Don't process events for completed campaigns
if c.Status == models.CAMPAIGN_COMPLETE {
http.NotFound(w, r)
return
}
c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_OPENED})
// Don't update the status if the user already clicked the link
// or submitted data to the campaign
Expand Down Expand Up @@ -157,11 +163,16 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
return
}
rs.UpdateStatus(models.STATUS_SUCCESS)
c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
if err != nil {
Logger.Println(err)
}
// Don't process events for completed campaigns
if c.Status == models.CAMPAIGN_COMPLETE {
http.NotFound(w, r)
return
}
rs.UpdateStatus(models.STATUS_SUCCESS)
p, err := models.GetPage(c.PageId, c.UserId)
if err != nil {
Logger.Println(err)
Expand Down
23 changes: 22 additions & 1 deletion models/campaign.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,29 @@ func DeleteCampaign(id int64) error {
// Delete the campaign
err = db.Delete(&Campaign{Id: id}).Error
if err != nil {
Logger.Panicln(err)
Logger.Println(err)
}
return err
}

// CompleteCampaign effectively "ends" a campaign.
// Any future emails clicked will return a simple "404" page.
func CompleteCampaign(id int64, uid int64) error {
Logger.Printf("Marking campaign %d as complete\n", id)
c, err := GetCampaign(id, uid)
if err != nil {
return err
}
// Don't overwrite original completed time
if c.Status == CAMPAIGN_COMPLETE {
return nil
}
// Mark the campaign as complete
c.CompletedDate = time.Now()
c.Status = CAMPAIGN_COMPLETE
err = db.Where("id=? and user_id=?", id, uid).Save(&c).Error
if err != nil {
Logger.Println(err)
}
return err
}
9 changes: 8 additions & 1 deletion static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,14 @@ td.details-control{
margin-left:10px !important;
}
}

table.dataTable{
width:100% !important;
}
.btn-blue {
color:#fff;
background-color:#428bca;
border-color:#428bca;
}
.btn-blue:hover{
background-color:#64a1d6;
}
1 change: 1 addition & 0 deletions static/css/sweetalert2.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 56 additions & 2 deletions static/js/app/campaign_results.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var map = null
var doPoll = true;

// statuses is a helper map to point result statuses to ui classes
var statuses = {
Expand Down Expand Up @@ -94,6 +95,52 @@ function deleteCampaign() {
}
}

// Completes a campaign after prompting the user
function completeCampaign() {
swal({
title: "Are you sure?",
text: "Gophish will stop processing events for this campaign",
type: "warning",
animation: false,
showCancelButton: true,
confirmButtonText: "Complete Campaign",
confirmButtonColor: "#428bca",
reverseButtons: true,
allowOutsideClick: false,
preConfirm: function() {
return new Promise(function(resolve, reject) {
api.campaignId.complete(campaign.id)
.success(function(msg) {
resolve()
})
.error(function(data) {
reject(data.responseJSON.message)
})
})
}
}).then(function() {
swal(
'Campaign Completed!',
'This campaign has been completed!',
'success'
);
$('#complete_button')[0].disabled = true;
$('#complete_button').text('Completed!')
doPoll = false;
})
/*
if (confirm("Are you sure you want to delete: " + campaign.name + "?")) {
api.campaignId.delete(campaign.id)
.success(function(msg) {
location.href = '/campaigns'
})
.error(function(e) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>")
})
}*/
}

// Exports campaign results as a CSV file
function exportAsCSV(scope) {
exportHTML = $("#exportButton").html()
Expand Down Expand Up @@ -298,7 +345,12 @@ function load() {
$("#campaignResults").show()
// Set the title
$("#page-title").text("Results for " + c.name)
// Setup tooltips
if (c.status == "Completed") {
$('#complete_button')[0].disabled = true;
$('#complete_button').text('Completed!');
doPoll = false;
}
// Setup tooltips
$('[data-toggle="tooltip"]').tooltip()
// Setup viewing the details of a result
$("#resultsTable").on("click", ".timeline-event-details", function() {
Expand Down Expand Up @@ -554,11 +606,13 @@ function load() {
errorFlash(" Campaign not found!")
})
}

$(document).ready(function() {
load();
// Start the polling loop
function refresh() {
if (!doPoll) {
return;
}
$("#refresh_message").show()
poll()
$("#refresh_message").hide()
Expand Down
4 changes: 4 additions & 0 deletions static/js/gophish.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ var api = {
// results() - Queries the API for GET /campaigns/:id/results
results: function(id) {
return query("/campaigns/" + id + "/results", "GET", {}, true)
},
// complete() - Completes a campaign at POST /campaigns/:id/complete
complete: function(id) {
return query("/campaigns/" + id + "/complete", "GET", {}, true)
}
},
// groups contains the endpoints for /groups
Expand Down
1 change: 1 addition & 0 deletions static/js/sweetalert2.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<link href='https://fonts.googleapis.com/css?family=Roboto:700,500' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,600,700' rel='stylesheet' type='text/css'>
<link href="/css/checkbox.css" rel="stylesheet">
<link href="/css/sweetalert2.min.css" rel="stylesheet">
<script>
{{if .User}}
var user = {
Expand Down
6 changes: 5 additions & 1 deletion templates/campaign_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ <h1 class="page-header" id="page-title">Results for campaign.name</h1>
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
</ul>
</div>
<button id="complete_button" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="completeCampaign()">
<i class="fa fa-flag-checkered"></i> Complete
</button>
<button type="button" class="btn btn-danger" data-toggle="tooltip" onclick="deleteCampaign()">
<i class="fa fa-trash-o fa-lg"></i> Delete
</button>
Expand Down Expand Up @@ -100,7 +103,7 @@ <h2>Details</h2>
<table id="resultsTable" class="table">
<thead>
<tr>
<th>Result ID</th>
<th>Result ID</th>
<th class="no-sort"></th>
<th>First Name</th>
<th>Last Name</th>
Expand All @@ -124,5 +127,6 @@ <h2>Details</h2>
<script src="/js/topojson.min.js"></script>
<script src="/js/datamaps.min.js"></script>
<script src="/js/papaparse.min.js"></script>
<script src="/js/sweetalert2.min.js"></script>
<script src="/js/app/campaign_results.js"></script>
{{end}}
Loading

0 comments on commit 1dbf061

Please sign in to comment.