From c5d6792bbaf1dd0223cdf6180d1d7dabf857b1ca Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Tue, 7 Jun 2016 22:31:55 -0500 Subject: [PATCH] Added /campaigns/:id/results endpoint to return campaign summary and make results page much quicker. Fixes 282. --- controllers/api.go | 17 +++++++++++++++++ controllers/route.go | 1 + models/campaign.go | 31 ++++++++++++++++++++++++++++++- static/js/app/campaign_results.js | 4 ++-- static/js/gophish.js | 16 ++++++++++------ 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/controllers/api.go b/controllers/api.go index ca7ce2d0..b2eb4030 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -111,6 +111,23 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) { } } +// API_Campaigns_Id_Results returns just the results for a given campaign to +// significantly reduce the information returned. +func API_Campaigns_Id_Results(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, _ := strconv.ParseInt(vars["id"], 0, 64) + cr, err := models.GetCampaignResults(id, ctx.Get(r, "user_id").(int64)) + if err != nil { + Logger.Println(err) + JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound) + return + } + if r.Method == "GET" { + JSONResponse(w, cr, http.StatusOK) + return + } +} + // API_Groups returns details about the requested group. If the campaign is not // valid, API_Groups returns null. func API_Groups(w http.ResponseWriter, r *http.Request) { diff --git a/controllers/route.go b/controllers/route.go index 257663fc..3f5e0267 100644 --- a/controllers/route.go +++ b/controllers/route.go @@ -46,6 +46,7 @@ func CreateAdminRouter() http.Handler { api.HandleFunc("/reset", Use(API_Reset, mid.RequireLogin)) 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("/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)) diff --git a/models/campaign.go b/models/campaign.go index e62eaa3d..ec91c3ef 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -7,7 +7,7 @@ import ( "github.com/jinzhu/gorm" ) -//Campaign is a struct representing a created campaign +// Campaign is a struct representing a created campaign type Campaign struct { Id int64 `json:"id"` UserId int64 `json:"-"` @@ -28,6 +28,15 @@ type Campaign struct { URL string `json:"url"` } +// CampaignResults is a struct representing the results from a campaign +type CampaignResults struct { + Id int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Results []Result `json:"results, omitempty"` + Events []Event `json:"timeline,omitempty"` +} + // ErrCampaignNameNotSpecified indicates there was no template given by the user var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified") @@ -190,6 +199,26 @@ func GetCampaign(id int64, uid int64) (Campaign, error) { return c, err } +func GetCampaignResults(id int64, uid int64) (CampaignResults, error) { + cr := CampaignResults{} + err := db.Table("campaigns").Where("id=? and user_id=?", id, uid).Find(&cr).Error + if err != nil { + Logger.Printf("%s: campaign not found\n", err) + return cr, err + } + err = db.Table("results").Where("campaign_id=? and user_id=?", cr.Id, uid).Find(&cr.Results).Error + if err != nil { + Logger.Printf("%s: results not found for campaign\n", err) + return cr, err + } + err = db.Table("events").Where("campaign_id=?", cr.Id).Find(&cr.Events).Error + if err != nil { + Logger.Printf("%s: events not found for campaign\n", err) + return cr, err + } + return cr, err +} + // GetQueuedCampaigns returns the campaigns that are queued up for this given minute func GetQueuedCampaigns(t time.Time) ([]Campaign, error) { cs := []Campaign{} diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index c130f619..fc0a48d0 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -191,7 +191,7 @@ function renderTimeline(data) { * * Datatables */ function poll() { - api.campaignId.get(campaign.id) + api.campaignId.results(campaign.id) .success(function(c) { campaign = c /* Update the timeline */ @@ -289,7 +289,7 @@ function poll() { function load() { campaign.id = window.location.pathname.split('/').slice(-1)[0] - api.campaignId.get(campaign.id) + api.campaignId.results(campaign.id) .success(function(c) { campaign = c if (campaign) { diff --git a/static/js/gophish.js b/static/js/gophish.js index ca8783fc..7f6e5474 100644 --- a/static/js/gophish.js +++ b/static/js/gophish.js @@ -26,12 +26,12 @@ function query(endpoint, method, data, async) { }) } -function escapeHtml(text){ - return $("
").text(text).html() +function escapeHtml(text) { + return $("
").text(text).html() } -function unescapeHtml(html){ - return $("
").html(html).text() +function unescapeHtml(html) { + return $("
").html(html).text() } /* @@ -58,6 +58,10 @@ var api = { // delete() - Deletes a campaign at DELETE /campaigns/:id delete: function(id) { return query("/campaigns/" + id, "DELETE", {}, false) + }, + // results() - Queries the API for GET /campaigns/:id/results + results: function(id) { + return query("/campaigns/" + id + "/results", "GET", {}, true) } }, // groups contains the endpoints for /groups @@ -173,8 +177,8 @@ var api = { return query("/import/site", "POST", req, false) }, // send_test_email sends an email to the specified email address - send_test_email: function(req){ - return query("/util/send_test_email", "POST", req, true) + send_test_email: function(req) { + return query("/util/send_test_email", "POST", req, true) } }