mirror of
https://github.com/gophish/gophish
synced 2024-11-14 16:27:23 +00:00
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.
This commit is contained in:
parent
222399c5f6
commit
420410b52c
10 changed files with 230 additions and 147 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -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 a new issue