mirror of https://github.com/gophish/gophish
Refactored result updating to be in result.go.
Added the modified_date field to results so it's easy to keep track of the last results that were modified without having to parse every event. Updated the tests to reflect the changes.pull/1090/head
parent
222399c5f6
commit
420410b52c
|
@ -26,4 +26,5 @@ gophish_admin.crt
|
||||||
gophish_admin.key
|
gophish_admin.key
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.db
|
gophish.db*
|
||||||
|
gophish
|
|
@ -2,7 +2,6 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -26,13 +25,6 @@ var ErrInvalidRequest = errors.New("Invalid request")
|
||||||
// has already been marked as complete.
|
// has already been marked as complete.
|
||||||
var ErrCampaignComplete = errors.New("Event received on completed campaign")
|
var ErrCampaignComplete = errors.New("Event received on completed campaign")
|
||||||
|
|
||||||
// eventDetails is a struct that wraps common attributes we want to store
|
|
||||||
// in an event
|
|
||||||
type eventDetails struct {
|
|
||||||
Payload url.Values `json:"payload"`
|
|
||||||
Browser map[string]string `json:"browser"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePhishingRouter creates the router that handles phishing connections.
|
// CreatePhishingRouter creates the router that handles phishing connections.
|
||||||
func CreatePhishingRouter() http.Handler {
|
func CreatePhishingRouter() http.Handler {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
@ -59,16 +51,8 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
rj := ctx.Get(r, "details").([]byte)
|
err = rs.HandleEmailOpened(d)
|
||||||
c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_OPENED, Details: string(rj)})
|
|
||||||
// Don't update the status if the user already clicked the link
|
|
||||||
// or submitted data to the campaign
|
|
||||||
if rs.Status == models.EVENT_CLICKED || rs.Status == models.EVENT_DATA_SUBMIT {
|
|
||||||
http.ServeFile(w, r, "static/images/pixel.png")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = rs.UpdateStatus(models.EVENT_OPENED)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -87,11 +71,9 @@ func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
rj := ctx.Get(r, "details").([]byte)
|
|
||||||
c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_REPORTED, Details: string(rj)})
|
|
||||||
|
|
||||||
err = rs.UpdateReported(true)
|
err = rs.HandleEmailReport(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +94,7 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
rs := ctx.Get(r, "result").(models.Result)
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
c := ctx.Get(r, "campaign").(models.Campaign)
|
||||||
rj := ctx.Get(r, "details").([]byte)
|
d := ctx.Get(r, "details").(models.EventDetails)
|
||||||
p, err := models.GetPage(c.PageId, c.UserId)
|
p, err := models.GetPage(c.PageId, c.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -121,18 +103,12 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case r.Method == "GET":
|
case r.Method == "GET":
|
||||||
if rs.Status != models.EVENT_CLICKED && rs.Status != models.EVENT_DATA_SUBMIT {
|
err = rs.HandleClickedLink(d)
|
||||||
rs.UpdateStatus(models.EVENT_CLICKED)
|
|
||||||
}
|
|
||||||
err = c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_CLICKED, Details: string(rj)})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
case r.Method == "POST":
|
case r.Method == "POST":
|
||||||
// If data was POST'ed, let's record it
|
err = rs.HandleFormSubmit(d)
|
||||||
rs.UpdateStatus(models.EVENT_DATA_SUBMIT)
|
|
||||||
// Store the data in an event
|
|
||||||
c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_DATA_SUBMIT, Details: string(rj)})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -224,16 +200,15 @@ func setupContext(r *http.Request) (error, *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
d := eventDetails{
|
d := models.EventDetails{
|
||||||
Payload: r.Form,
|
Payload: r.Form,
|
||||||
Browser: make(map[string]string),
|
Browser: make(map[string]string),
|
||||||
}
|
}
|
||||||
d.Browser["address"] = ip
|
d.Browser["address"] = ip
|
||||||
d.Browser["user-agent"] = r.Header.Get("User-Agent")
|
d.Browser["user-agent"] = r.Header.Get("User-Agent")
|
||||||
rj, err := json.Marshal(d)
|
|
||||||
|
|
||||||
r = ctx.Set(r, "result", rs)
|
r = ctx.Set(r, "result", rs)
|
||||||
r = ctx.Set(r, "campaign", c)
|
r = ctx.Set(r, "campaign", c)
|
||||||
r = ctx.Set(r, "details", rj)
|
r = ctx.Set(r, "details", d)
|
||||||
return nil, r
|
return nil, r
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,10 @@ func (s *ControllersSuite) TestOpenedPhishingEmail() {
|
||||||
|
|
||||||
campaign = s.getFirstCampaign()
|
campaign = s.getFirstCampaign()
|
||||||
result = campaign.Results[0]
|
result = campaign.Results[0]
|
||||||
|
lastEvent := campaign.Events[len(campaign.Events)-1]
|
||||||
s.Equal(result.Status, models.EVENT_OPENED)
|
s.Equal(result.Status, models.EVENT_OPENED)
|
||||||
|
s.Equal(lastEvent.Message, models.EVENT_OPENED)
|
||||||
|
s.Equal(result.ModifiedDate, lastEvent.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) TestReportedPhishingEmail() {
|
func (s *ControllersSuite) TestReportedPhishingEmail() {
|
||||||
|
@ -78,8 +81,10 @@ func (s *ControllersSuite) TestReportedPhishingEmail() {
|
||||||
|
|
||||||
campaign = s.getFirstCampaign()
|
campaign = s.getFirstCampaign()
|
||||||
result = campaign.Results[0]
|
result = campaign.Results[0]
|
||||||
|
lastEvent := campaign.Events[len(campaign.Events)-1]
|
||||||
s.Equal(result.Reported, true)
|
s.Equal(result.Reported, true)
|
||||||
s.Equal(campaign.Events[len(campaign.Events)-1].Message, models.EVENT_REPORTED)
|
s.Equal(lastEvent.Message, models.EVENT_REPORTED)
|
||||||
|
s.Equal(result.ModifiedDate, lastEvent.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
||||||
|
@ -92,7 +97,10 @@ func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
||||||
|
|
||||||
campaign = s.getFirstCampaign()
|
campaign = s.getFirstCampaign()
|
||||||
result = campaign.Results[0]
|
result = campaign.Results[0]
|
||||||
|
lastEvent := campaign.Events[len(campaign.Events)-1]
|
||||||
s.Equal(result.Status, models.EVENT_CLICKED)
|
s.Equal(result.Status, models.EVENT_CLICKED)
|
||||||
|
s.Equal(lastEvent.Message, models.EVENT_CLICKED)
|
||||||
|
s.Equal(result.ModifiedDate, lastEvent.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) TestNoRecipientID() {
|
func (s *ControllersSuite) TestNoRecipientID() {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
ALTER TABLE results ADD COLUMN modified_date DATETIME;
|
||||||
|
|
||||||
|
UPDATE results
|
||||||
|
SET `modified_date`= (
|
||||||
|
SELECT max(events.time) FROM events
|
||||||
|
WHERE events.email=results.email
|
||||||
|
AND events.campaign_id=results.campaign_id
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
ALTER TABLE results ADD COLUMN modified_date DATETIME;
|
||||||
|
|
||||||
|
UPDATE results
|
||||||
|
SET `modified_date`= (
|
||||||
|
SELECT max(events.time) FROM events
|
||||||
|
WHERE events.email=results.email
|
||||||
|
AND events.campaign_id=results.campaign_id
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -2,6 +2,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/gophish/gophish/logger"
|
log "github.com/gophish/gophish/logger"
|
||||||
|
@ -68,6 +69,30 @@ type CampaignStats struct {
|
||||||
Error int64 `json:"error"`
|
Error int64 `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event contains the fields for an event
|
||||||
|
// that occurs during the campaign
|
||||||
|
type Event struct {
|
||||||
|
Id int64 `json:"-"`
|
||||||
|
CampaignId int64 `json:"-"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventDetails is a struct that wraps common attributes we want to store
|
||||||
|
// in an event
|
||||||
|
type EventDetails struct {
|
||||||
|
Payload url.Values `json:"payload"`
|
||||||
|
Browser map[string]string `json:"browser"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventError is a struct that wraps an error that occurs when sending an
|
||||||
|
// email to a recipient
|
||||||
|
type EventError struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
// ErrCampaignNameNotSpecified indicates there was no template given by the user
|
// ErrCampaignNameNotSpecified indicates there was no template given by the user
|
||||||
var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified")
|
var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified")
|
||||||
|
|
||||||
|
@ -122,10 +147,10 @@ func (c *Campaign) UpdateStatus(s string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddEvent creates a new campaign event in the database
|
// AddEvent creates a new campaign event in the database
|
||||||
func (c *Campaign) AddEvent(e Event) error {
|
func (c *Campaign) AddEvent(e *Event) error {
|
||||||
e.CampaignId = c.Id
|
e.CampaignId = c.Id
|
||||||
e.Time = time.Now().UTC()
|
e.Time = time.Now().UTC()
|
||||||
return db.Save(&e).Error
|
return db.Save(e).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDetails retrieves the related attributes of the campaign
|
// getDetails retrieves the related attributes of the campaign
|
||||||
|
@ -220,17 +245,6 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event contains the fields for an event
|
|
||||||
// that occurs during the campaign
|
|
||||||
type Event struct {
|
|
||||||
Id int64 `json:"-"`
|
|
||||||
CampaignId int64 `json:"-"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Time time.Time `json:"time"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Details string `json:"details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCampaigns returns the campaigns owned by the given user.
|
// GetCampaigns returns the campaigns owned by the given user.
|
||||||
func GetCampaigns(uid int64) ([]Campaign, error) {
|
func GetCampaigns(uid int64) ([]Campaign, error) {
|
||||||
cs := []Campaign{}
|
cs := []Campaign{}
|
||||||
|
@ -422,7 +436,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = c.AddEvent(Event{Message: "Campaign Created"})
|
err = c.AddEvent(&Event{Message: "Campaign Created"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -438,15 +452,16 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
}
|
}
|
||||||
resultMap[t.Email] = true
|
resultMap[t.Email] = true
|
||||||
r := &Result{
|
r := &Result{
|
||||||
Email: t.Email,
|
Email: t.Email,
|
||||||
Position: t.Position,
|
Position: t.Position,
|
||||||
Status: STATUS_SCHEDULED,
|
Status: STATUS_SCHEDULED,
|
||||||
CampaignId: c.Id,
|
CampaignId: c.Id,
|
||||||
UserId: c.UserId,
|
UserId: c.UserId,
|
||||||
FirstName: t.FirstName,
|
FirstName: t.FirstName,
|
||||||
LastName: t.LastName,
|
LastName: t.LastName,
|
||||||
SendDate: c.LaunchDate,
|
SendDate: c.LaunchDate,
|
||||||
Reported: false,
|
Reported: false,
|
||||||
|
ModifiedDate: c.CreatedDate,
|
||||||
}
|
}
|
||||||
if c.Status == CAMPAIGN_IN_PROGRESS {
|
if c.Status == CAMPAIGN_IN_PROGRESS {
|
||||||
r.Status = STATUS_SENDING
|
r.Status = STATUS_SENDING
|
||||||
|
|
|
@ -3,7 +3,6 @@ package models
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -58,20 +57,16 @@ func GenerateMailLog(c *Campaign, r *Result) error {
|
||||||
// too many times. Backoff also unlocks the maillog so that it can be processed
|
// too many times. Backoff also unlocks the maillog so that it can be processed
|
||||||
// again in the future.
|
// again in the future.
|
||||||
func (m *MailLog) Backoff(reason error) error {
|
func (m *MailLog) Backoff(reason error) error {
|
||||||
if m.SendAttempt == MaxSendAttempts {
|
|
||||||
err = m.addError(ErrMaxSendAttempts)
|
|
||||||
return ErrMaxSendAttempts
|
|
||||||
}
|
|
||||||
r, err := GetResult(m.RId)
|
r, err := GetResult(m.RId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if m.SendAttempt == MaxSendAttempts {
|
||||||
|
r.HandleEmailError(ErrMaxSendAttempts)
|
||||||
|
return ErrMaxSendAttempts
|
||||||
|
}
|
||||||
// Add an error, since we had to backoff because of a
|
// Add an error, since we had to backoff because of a
|
||||||
// temporary error of some sort during the SMTP transaction
|
// temporary error of some sort during the SMTP transaction
|
||||||
err = m.addError(reason)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.SendAttempt++
|
m.SendAttempt++
|
||||||
backoffDuration := math.Pow(2, float64(m.SendAttempt))
|
backoffDuration := math.Pow(2, float64(m.SendAttempt))
|
||||||
m.SendDate = m.SendDate.Add(time.Minute * time.Duration(backoffDuration))
|
m.SendDate = m.SendDate.Add(time.Minute * time.Duration(backoffDuration))
|
||||||
|
@ -79,9 +74,7 @@ func (m *MailLog) Backoff(reason error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.Status = STATUS_RETRY
|
err = r.HandleEmailBackoff(reason, m.SendDate)
|
||||||
r.SendDate = m.SendDate
|
|
||||||
err = db.Save(r).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -101,32 +94,6 @@ func (m *MailLog) Lock() error {
|
||||||
return db.Save(&m).Error
|
return db.Save(&m).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// addError adds an error to the associated campaign
|
|
||||||
func (m *MailLog) addError(e error) error {
|
|
||||||
c, err := GetCampaign(m.CampaignId, m.UserId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// This is redundant in the case of permanent
|
|
||||||
// errors, but the extra query makes for
|
|
||||||
// a cleaner API.
|
|
||||||
r, err := GetResult(m.RId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
es := struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}{
|
|
||||||
Error: e.Error(),
|
|
||||||
}
|
|
||||||
ej, err := json.Marshal(es)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
}
|
|
||||||
err = c.AddEvent(Event{Email: r.Email, Message: EVENT_SENDING_ERROR, Details: string(ej)})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error sets the error status on the models.Result that the
|
// Error sets the error status on the models.Result that the
|
||||||
// maillog refers to. Since MailLog errors are permanent,
|
// maillog refers to. Since MailLog errors are permanent,
|
||||||
// this action also deletes the maillog.
|
// this action also deletes the maillog.
|
||||||
|
@ -136,14 +103,7 @@ func (m *MailLog) Error(e error) error {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Update the result
|
err = r.HandleEmailError(e)
|
||||||
err = r.UpdateStatus(ERROR)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Update the campaign events
|
|
||||||
err = m.addError(e)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
return err
|
return err
|
||||||
|
@ -159,15 +119,7 @@ func (m *MailLog) Success() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = r.UpdateStatus(EVENT_SENT)
|
err = r.HandleEmailSent()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c, err := GetCampaign(m.CampaignId, m.UserId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.AddEvent(Event{Email: r.Email, Message: EVENT_SENT})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,11 +105,7 @@ func (s *ModelsSuite) TestMailLogError(ch *check.C) {
|
||||||
ch.Assert(len(campaign.Events), check.Equals, expectedEventLength)
|
ch.Assert(len(campaign.Events), check.Equals, expectedEventLength)
|
||||||
|
|
||||||
gotEvent := campaign.Events[1]
|
gotEvent := campaign.Events[1]
|
||||||
es := struct {
|
es := EventError{Error: expectedError.Error()}
|
||||||
Error string `json:"error"`
|
|
||||||
}{
|
|
||||||
Error: expectedError.Error(),
|
|
||||||
}
|
|
||||||
ej, _ := json.Marshal(es)
|
ej, _ := json.Marshal(es)
|
||||||
expectedEvent := Event{
|
expectedEvent := Event{
|
||||||
Id: gotEvent.Id,
|
Id: gotEvent.Id,
|
||||||
|
|
153
models/result.go
153
models/result.go
|
@ -2,6 +2,7 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
@ -25,30 +26,133 @@ type mmGeoPoint struct {
|
||||||
// Result contains the fields for a result object,
|
// Result contains the fields for a result object,
|
||||||
// which is a representation of a target in a campaign.
|
// which is a representation of a target in a campaign.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Id int64 `json:"-"`
|
Id int64 `json:"-"`
|
||||||
CampaignId int64 `json:"-"`
|
CampaignId int64 `json:"-"`
|
||||||
UserId int64 `json:"-"`
|
UserId int64 `json:"-"`
|
||||||
RId string `json:"id"`
|
RId string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
Position string `json:"position"`
|
Position string `json:"position"`
|
||||||
Status string `json:"status" sql:"not null"`
|
Status string `json:"status" sql:"not null"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
SendDate time.Time `json:"send_date"`
|
SendDate time.Time `json:"send_date"`
|
||||||
Reported bool `json:"reported" sql:"not null"`
|
Reported bool `json:"reported" sql:"not null"`
|
||||||
|
ModifiedDate time.Time `json:"modified_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStatus updates the status of the result in the database
|
func (r *Result) createEvent(status string, details interface{}) (*Event, error) {
|
||||||
func (r *Result) UpdateStatus(s string) error {
|
c, err := GetCampaign(r.CampaignId, r.UserId)
|
||||||
return db.Table("results").Where("id=?", r.Id).Update("status", s).Error
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e := &Event{Email: r.Email, Message: status}
|
||||||
|
if details != nil {
|
||||||
|
dj, err := json.Marshal(details)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.Details = string(dj)
|
||||||
|
}
|
||||||
|
c.AddEvent(e)
|
||||||
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateReported updates when a user reports a campaign
|
// HandleEmailSent updates a Result to indicate that the email has been
|
||||||
func (r *Result) UpdateReported(s bool) error {
|
// successfully sent to the remote SMTP server
|
||||||
return db.Table("results").Where("id=?", r.Id).Update("reported", s).Error
|
func (r *Result) HandleEmailSent() error {
|
||||||
|
event, err := r.createEvent(EVENT_SENT, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Status = EVENT_SENT
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleEmailError updates a Result to indicate that there was an error when
|
||||||
|
// attempting to send the email to the remote SMTP server.
|
||||||
|
func (r *Result) HandleEmailError(err error) error {
|
||||||
|
event, err := r.createEvent(EVENT_SENDING_ERROR, EventError{Error: err.Error()})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Status = ERROR
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleEmailBackoff updates a Result to indicate that the email received a
|
||||||
|
// temporary error and needs to be retried
|
||||||
|
func (r *Result) HandleEmailBackoff(err error, sendDate time.Time) error {
|
||||||
|
event, err := r.createEvent(EVENT_SENDING_ERROR, EventError{Error: err.Error()})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Status = STATUS_RETRY
|
||||||
|
r.SendDate = sendDate
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleEmailOpened updates a Result in the case where the recipient opened the
|
||||||
|
// email.
|
||||||
|
func (r *Result) HandleEmailOpened(details EventDetails) error {
|
||||||
|
event, err := r.createEvent(EVENT_OPENED, details)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Don't update the status if the user already clicked the link
|
||||||
|
// or submitted data to the campaign
|
||||||
|
if r.Status == EVENT_CLICKED || r.Status == EVENT_DATA_SUBMIT {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.Status = EVENT_OPENED
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleClickedLink updates a Result in the case where the recipient clicked
|
||||||
|
// the link in an email.
|
||||||
|
func (r *Result) HandleClickedLink(details EventDetails) error {
|
||||||
|
event, err := r.createEvent(EVENT_CLICKED, details)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Don't update the status if the user has already submitted data via the
|
||||||
|
// landing page form.
|
||||||
|
if r.Status == EVENT_DATA_SUBMIT {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.Status = EVENT_CLICKED
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFormSubmit updates a Result in the case where the recipient submitted
|
||||||
|
// credentials to the form on a Landing Page.
|
||||||
|
func (r *Result) HandleFormSubmit(details EventDetails) error {
|
||||||
|
event, err := r.createEvent(EVENT_DATA_SUBMIT, details)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Status = EVENT_DATA_SUBMIT
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleEmailReport updates a Result in the case where they report a simulated
|
||||||
|
// phishing email using the HTTP handler.
|
||||||
|
func (r *Result) HandleEmailReport(details EventDetails) error {
|
||||||
|
event, err := r.createEvent(EVENT_REPORTED, details)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Reported = true
|
||||||
|
r.ModifiedDate = event.Time
|
||||||
|
return db.Save(r).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateGeo updates the latitude and longitude of the result in
|
// UpdateGeo updates the latitude and longitude of the result in
|
||||||
|
@ -68,11 +172,10 @@ func (r *Result) UpdateGeo(addr string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Update the database with the record information
|
// Update the database with the record information
|
||||||
return db.Table("results").Where("id=?", r.Id).Updates(map[string]interface{}{
|
r.IP = addr
|
||||||
"ip": addr,
|
r.Latitude = city.GeoPoint.Latitude
|
||||||
"latitude": city.GeoPoint.Latitude,
|
r.Longitude = city.GeoPoint.Longitude
|
||||||
"longitude": city.GeoPoint.Longitude,
|
return db.Save(r).Error
|
||||||
}).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateId generates a unique key to represent the result
|
// GenerateId generates a unique key to represent the result
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
@ -40,10 +39,9 @@ func (s *ModelsSuite) TestResultSendingStatus(ch *check.C) {
|
||||||
ch.Assert(PostCampaign(&c, c.UserId), check.Equals, nil)
|
ch.Assert(PostCampaign(&c, c.UserId), check.Equals, nil)
|
||||||
// This campaign wasn't scheduled, so we expect the status to
|
// This campaign wasn't scheduled, so we expect the status to
|
||||||
// be sending
|
// be sending
|
||||||
fmt.Println("Campaign STATUS")
|
|
||||||
fmt.Println(c.Status)
|
|
||||||
for _, r := range c.Results {
|
for _, r := range c.Results {
|
||||||
ch.Assert(r.Status, check.Equals, STATUS_SENDING)
|
ch.Assert(r.Status, check.Equals, STATUS_SENDING)
|
||||||
|
ch.Assert(r.ModifiedDate, check.Equals, c.CreatedDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *ModelsSuite) TestResultScheduledStatus(ch *check.C) {
|
func (s *ModelsSuite) TestResultScheduledStatus(ch *check.C) {
|
||||||
|
@ -54,6 +52,7 @@ func (s *ModelsSuite) TestResultScheduledStatus(ch *check.C) {
|
||||||
// be sending
|
// be sending
|
||||||
for _, r := range c.Results {
|
for _, r := range c.Results {
|
||||||
ch.Assert(r.Status, check.Equals, STATUS_SCHEDULED)
|
ch.Assert(r.Status, check.Equals, STATUS_SCHEDULED)
|
||||||
|
ch.Assert(r.ModifiedDate, check.Equals, c.CreatedDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue