mirror of https://github.com/gophish/gophish
Implemented the ability to preview landing pages when sending a test email.
parent
a04f6d031b
commit
ebb6cd61b2
|
@ -652,8 +652,9 @@ func API_Import_Site(w http.ResponseWriter, r *http.Request) {
|
|||
// API_Send_Test_Email sends a test email using the template name
|
||||
// and Target given.
|
||||
func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
||||
s := &models.SendTestEmailRequest{
|
||||
s := &models.EmailRequest{
|
||||
ErrorChan: make(chan error),
|
||||
UserId: ctx.Get(r, "user_id").(int64),
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
||||
|
@ -664,11 +665,8 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
|||
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Validate the given request
|
||||
if err = s.Validate(); err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
storeRequest := false
|
||||
|
||||
// If a Template is not specified use a default
|
||||
if s.Template.Name == "" {
|
||||
|
@ -678,17 +676,15 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
|||
"{{if .FirstName}} First Name: {{.FirstName}}\n{{end}}" +
|
||||
"{{if .LastName}} Last Name: {{.LastName}}\n{{end}}" +
|
||||
"{{if .Position}} Position: {{.Position}}\n{{end}}" +
|
||||
"{{if .TrackingURL}} Tracking URL: {{.TrackingURL}}\n{{end}}" +
|
||||
"\nNow go send some phish!"
|
||||
t := models.Template{
|
||||
Subject: "Default Email from Gophish",
|
||||
Text: text,
|
||||
}
|
||||
s.Template = t
|
||||
// Try to lookup the Template by name
|
||||
} else {
|
||||
// Get the Template requested by name
|
||||
s.Template, err = models.GetTemplateByName(s.Template.Name, ctx.Get(r, "user_id").(int64))
|
||||
s.Template, err = models.GetTemplateByName(s.Template.Name, s.UserId)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
log.WithFields(logrus.Fields{
|
||||
"template": s.Template.Name,
|
||||
|
@ -700,12 +696,32 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
|||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.TemplateId = s.Template.Id
|
||||
// We'll only save the test request to the database if there is a
|
||||
// user-specified template to use.
|
||||
storeRequest = true
|
||||
}
|
||||
|
||||
if s.Page.Name != "" {
|
||||
s.Page, err = models.GetPageByName(s.Page.Name, s.UserId)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
log.WithFields(logrus.Fields{
|
||||
"page": s.Page.Name,
|
||||
}).Error("Page does not exist")
|
||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrPageNotFound.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Error(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.PageId = s.Page.Id
|
||||
}
|
||||
|
||||
// If a complete sending profile is provided use it
|
||||
if err := s.SMTP.Validate(); err != nil {
|
||||
// Otherwise get the SMTP requested by name
|
||||
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, ctx.Get(r, "user_id").(int64))
|
||||
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, s.UserId)
|
||||
// If the Sending Profile doesn't exist, let's err on the side
|
||||
// of caution and assume that the validation failure was more important.
|
||||
if lookupErr != nil {
|
||||
|
@ -716,9 +732,25 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
|||
s.SMTP = smtp
|
||||
}
|
||||
|
||||
// Validate the given request
|
||||
if err = s.Validate(); err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Store the request if this wasn't the default template
|
||||
if storeRequest {
|
||||
err = models.PostEmailRequest(s)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Send the test email
|
||||
err = Worker.SendTestEmail(s)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -63,8 +63,8 @@ func (s *ControllersSuite) SetupTest() {
|
|||
// Add a group
|
||||
group := models.Group{Name: "Test Group"}
|
||||
group.Targets = []models.Target{
|
||||
models.Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
models.Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
models.Target{BaseRecipient: models.BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
models.Target{BaseRecipient: models.BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
models.PostGroup(&group)
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
ctx "github.com/gophish/gophish/context"
|
||||
|
@ -50,6 +46,11 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
// Check for a preview
|
||||
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||
http.ServeFile(w, r, "static/images/pixel.png")
|
||||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
d := ctx.Get(r, "details").(models.EventDetails)
|
||||
err = rs.HandleEmailOpened(d)
|
||||
|
@ -70,6 +71,11 @@ func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
// Check for a preview
|
||||
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
d := ctx.Get(r, "details").(models.EventDetails)
|
||||
|
||||
|
@ -92,6 +98,24 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var ptx models.PhishingTemplateContext
|
||||
// Check for a preview
|
||||
if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
||||
ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
p, err := models.GetPage(preview.PageId, preview.UserId)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
renderPhishResponse(w, r, ptx, p)
|
||||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
||||
d := ctx.Get(r, "details").(models.EventDetails)
|
||||
|
@ -112,49 +136,35 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
// Redirect to the desired page
|
||||
}
|
||||
ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
renderPhishResponse(w, r, ptx, p)
|
||||
}
|
||||
|
||||
// renderPhishResponse handles rendering the correct response to the phishing
|
||||
// connection. This usually involves writing out the page HTML or redirecting
|
||||
// the user to the correct URL.
|
||||
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
|
||||
// If the request was a form submit and a redirect URL was specified, we
|
||||
// should send the user to that URL
|
||||
if r.Method == "POST" {
|
||||
if p.RedirectURL != "" {
|
||||
http.Redirect(w, r, p.RedirectURL, 302)
|
||||
return
|
||||
}
|
||||
}
|
||||
var htmlBuff bytes.Buffer
|
||||
tmpl, err := template.New("html_template").Parse(p.HTML)
|
||||
// Otherwise, we just need to write out the templated HTML
|
||||
html, err := models.ExecuteTemplate(p.HTML, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
fn := f.Name
|
||||
if fn == "" {
|
||||
fn = f.Address
|
||||
}
|
||||
|
||||
phishURL, _ := url.Parse(c.URL)
|
||||
q := phishURL.Query()
|
||||
q.Set(models.RecipientParameter, rs.RId)
|
||||
phishURL.RawQuery = q.Encode()
|
||||
|
||||
rsf := struct {
|
||||
models.Result
|
||||
URL string
|
||||
From string
|
||||
}{
|
||||
rs,
|
||||
phishURL.String(),
|
||||
fn,
|
||||
}
|
||||
err = tmpl.Execute(&htmlBuff, rsf)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Write(htmlBuff.Bytes())
|
||||
w.Write([]byte(html))
|
||||
}
|
||||
|
||||
// RobotsHandler prevents search engines, etc. from indexing phishing materials
|
||||
|
@ -173,6 +183,15 @@ func setupContext(r *http.Request) (error, *http.Request) {
|
|||
if id == "" {
|
||||
return ErrInvalidRequest, r
|
||||
}
|
||||
// Check to see if this is a preview or a real result
|
||||
if strings.HasPrefix(id, models.PreviewPrefix) {
|
||||
rs, err := models.GetEmailRequestByResultId(id)
|
||||
if err != nil {
|
||||
return err, r
|
||||
}
|
||||
r = ctx.Set(r, "result", rs)
|
||||
return nil, r
|
||||
}
|
||||
rs, err := models.GetResult(id)
|
||||
if err != nil {
|
||||
return err, r
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gophish/gophish/models"
|
||||
|
@ -15,6 +16,23 @@ func (s *ControllersSuite) getFirstCampaign() models.Campaign {
|
|||
return campaigns[0]
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) getFirstEmailRequest() models.EmailRequest {
|
||||
campaign := s.getFirstCampaign()
|
||||
req := models.EmailRequest{
|
||||
TemplateId: campaign.TemplateId,
|
||||
Template: campaign.Template,
|
||||
PageId: campaign.PageId,
|
||||
Page: campaign.Page,
|
||||
URL: "http://localhost.localdomain",
|
||||
UserId: 1,
|
||||
BaseRecipient: campaign.Results[0].BaseRecipient,
|
||||
SMTP: campaign.SMTP,
|
||||
}
|
||||
err := models.PostEmailRequest(&req)
|
||||
s.Nil(err)
|
||||
return req
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) openEmail(rid string) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
|
@ -40,13 +58,14 @@ func (s *ControllersSuite) openEmail404(rid string) {
|
|||
s.Equal(resp.StatusCode, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) clickLink(rid string, campaign models.Campaign) {
|
||||
func (s *ControllersSuite) clickLink(rid string, expectedHTML string) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
s.Nil(err)
|
||||
s.Equal(bytes.Compare(body, []byte(campaign.Page.HTML)), 0)
|
||||
log.Printf("%s\n\n\n", body)
|
||||
s.Equal(bytes.Compare(body, []byte(expectedHTML)), 0)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) clickLink404(rid string) {
|
||||
|
@ -93,7 +112,7 @@ func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
|||
s.Equal(result.Status, models.STATUS_SENDING)
|
||||
|
||||
s.openEmail(result.RId)
|
||||
s.clickLink(result.RId, campaign)
|
||||
s.clickLink(result.RId, campaign.Page.HTML)
|
||||
|
||||
campaign = s.getFirstCampaign()
|
||||
result = campaign.Results[0]
|
||||
|
@ -153,3 +172,19 @@ func (s *ControllersSuite) TestRobotsHandler() {
|
|||
s.Nil(err)
|
||||
s.Equal(bytes.Compare(body, expected), 0)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) TestInvalidPreviewID() {
|
||||
bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix)
|
||||
s.openEmail404(bogusRId)
|
||||
s.clickLink404(bogusRId)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) TestPreviewTrack() {
|
||||
req := s.getFirstEmailRequest()
|
||||
s.openEmail(req.RId)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) TestPreviewClick() {
|
||||
req := s.getFirstEmailRequest()
|
||||
s.clickLink(req.RId, req.Page.HTML)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
CREATE TABLE IF NOT EXISTS email_requests (
|
||||
`id` integer primary key auto_increment,
|
||||
`user_id` integer,
|
||||
`template_id` integer,
|
||||
`page_id` integer,
|
||||
`first_name` varchar(255),
|
||||
`last_name` varchar(255),
|
||||
`email` varchar(255),
|
||||
`position` varchar(255),
|
||||
`url` varchar(255),
|
||||
`r_id` varchar(255),
|
||||
`from_address` varchar(255)
|
||||
);
|
||||
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
DROP TABLE email_requests
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
CREATE TABLE IF NOT EXISTS "email_requests" (
|
||||
"id" integer primary key autoincrement,
|
||||
"user_id" integer,
|
||||
"template_id" integer,
|
||||
"page_id" integer,
|
||||
"first_name" varchar(255),
|
||||
"last_name" varchar(255),
|
||||
"email" varchar(255),
|
||||
"position" varchar(255),
|
||||
"url" varchar(255),
|
||||
"r_id" varchar(255),
|
||||
"from_address" varchar(255)
|
||||
);
|
||||
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
|
@ -206,6 +206,18 @@ func (c *Campaign) getDetails() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getBaseURL returns the Campaign's configured URL.
|
||||
// This is used to implement the TemplateContext interface.
|
||||
func (c *Campaign) getBaseURL() string {
|
||||
return c.URL
|
||||
}
|
||||
|
||||
// getFromAddress returns the Campaign's configured SMTP "From" address.
|
||||
// This is used to implement the TemplateContext interface.
|
||||
func (c *Campaign) getFromAddress() string {
|
||||
return c.SMTP.FromAddress
|
||||
}
|
||||
|
||||
// getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID.
|
||||
// It also backfills numbers as appropriate with a running total, so that the values are aggregated.
|
||||
func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||
|
@ -452,13 +464,15 @@ func PostCampaign(c *Campaign, uid int64) error {
|
|||
}
|
||||
resultMap[t.Email] = true
|
||||
r := &Result{
|
||||
Email: t.Email,
|
||||
Position: t.Position,
|
||||
BaseRecipient: BaseRecipient{
|
||||
Email: t.Email,
|
||||
Position: t.Position,
|
||||
FirstName: t.FirstName,
|
||||
LastName: t.LastName,
|
||||
},
|
||||
Status: STATUS_SCHEDULED,
|
||||
CampaignId: c.Id,
|
||||
UserId: c.UserId,
|
||||
FirstName: t.FirstName,
|
||||
LastName: t.LastName,
|
||||
SendDate: c.LaunchDate,
|
||||
Reported: false,
|
||||
ModifiedDate: c.CreatedDate,
|
||||
|
|
|
@ -12,55 +12,94 @@ import (
|
|||
"github.com/gophish/gophish/mailer"
|
||||
)
|
||||
|
||||
// SendTestEmailRequest is the structure of a request
|
||||
// PreviewPrefix is the standard prefix added to the rid parameter when sending
|
||||
// test emails.
|
||||
const PreviewPrefix = "preview-"
|
||||
|
||||
// EmailRequest is the structure of a request
|
||||
// to send a test email to test an SMTP connection.
|
||||
// This type implements the mailer.Mail interface.
|
||||
type SendTestEmailRequest struct {
|
||||
Template Template `json:"template"`
|
||||
Page Page `json:"page"`
|
||||
SMTP SMTP `json:"smtp"`
|
||||
URL string `json:"url"`
|
||||
Tracker string `json:"tracker"`
|
||||
TrackingURL string `json:"tracking_url"`
|
||||
From string `json:"from"`
|
||||
Target
|
||||
ErrorChan chan (error) `json:"-"`
|
||||
type EmailRequest struct {
|
||||
Id int64 `json:"-"`
|
||||
Template Template `json:"template"`
|
||||
TemplateId int64 `json:"-"`
|
||||
Page Page `json:"page"`
|
||||
PageId int64 `json:"-"`
|
||||
SMTP SMTP `json:"smtp"`
|
||||
URL string `json:"url"`
|
||||
Tracker string `json:"tracker" gorm:"-"`
|
||||
TrackingURL string `json:"tracking_url" gorm:"-"`
|
||||
UserId int64 `json:"-"`
|
||||
ErrorChan chan (error) `json:"-" gorm:"-"`
|
||||
RId string `json:"id"`
|
||||
FromAddress string `json:"-"`
|
||||
BaseRecipient
|
||||
}
|
||||
|
||||
func (s *EmailRequest) getBaseURL() string {
|
||||
return s.URL
|
||||
}
|
||||
|
||||
func (s *EmailRequest) getFromAddress() string {
|
||||
return s.FromAddress
|
||||
}
|
||||
|
||||
// Validate ensures the SendTestEmailRequest structure
|
||||
// is valid.
|
||||
func (s *SendTestEmailRequest) Validate() error {
|
||||
func (s *EmailRequest) Validate() error {
|
||||
switch {
|
||||
case s.Email == "":
|
||||
return ErrEmailNotSpecified
|
||||
case s.FromAddress == "" && s.SMTP.FromAddress == "":
|
||||
return ErrFromAddressNotSpecified
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Backoff treats temporary errors as permanent since this is expected to be a
|
||||
// synchronous operation. It returns any errors given back to the ErrorChan
|
||||
func (s *SendTestEmailRequest) Backoff(reason error) error {
|
||||
func (s *EmailRequest) Backoff(reason error) error {
|
||||
s.ErrorChan <- reason
|
||||
return nil
|
||||
}
|
||||
|
||||
// Error returns an error on the ErrorChan.
|
||||
func (s *SendTestEmailRequest) Error(err error) error {
|
||||
func (s *EmailRequest) Error(err error) error {
|
||||
s.ErrorChan <- err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Success returns nil on the ErrorChan to indicate that the email was sent
|
||||
// successfully.
|
||||
func (s *SendTestEmailRequest) Success() error {
|
||||
func (s *EmailRequest) Success() error {
|
||||
s.ErrorChan <- nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostEmailRequest stores a SendTestEmailRequest in the database.
|
||||
func PostEmailRequest(s *EmailRequest) error {
|
||||
// Generate an ID to be used in the underlying Result object
|
||||
rid, err := generateResultId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.RId = fmt.Sprintf("%s%s", PreviewPrefix, rid)
|
||||
s.FromAddress = s.SMTP.FromAddress
|
||||
return db.Save(&s).Error
|
||||
}
|
||||
|
||||
// GetEmailRequestByResultId retrieves the EmailRequest by the underlying rid
|
||||
// parameter.
|
||||
func GetEmailRequestByResultId(id string) (EmailRequest, error) {
|
||||
s := EmailRequest{}
|
||||
err := db.Table("email_requests").Where("r_id=?", id).First(&s).Error
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Generate fills in the details of a gomail.Message with the contents
|
||||
// from the SendTestEmailRequest.
|
||||
func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
||||
f, err := mail.ParseAddress(s.SMTP.FromAddress)
|
||||
func (s *EmailRequest) Generate(msg *gomail.Message) error {
|
||||
f, err := mail.ParseAddress(s.FromAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -70,7 +109,12 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
|||
}
|
||||
msg.SetAddressHeader("From", f.Address, f.Name)
|
||||
|
||||
url, err := buildTemplate(s.URL, s)
|
||||
ptx, err := NewPhishingTemplateContext(s, s.BaseRecipient, s.RId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url, err := ExecuteTemplate(s.URL, ptx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -78,12 +122,12 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
|||
|
||||
// Parse the customHeader templates
|
||||
for _, header := range s.SMTP.Headers {
|
||||
key, err := buildTemplate(header.Key, s)
|
||||
key, err := ExecuteTemplate(header.Key, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
value, err := buildTemplate(header.Value, s)
|
||||
value, err := ExecuteTemplate(header.Value, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
@ -93,7 +137,7 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
|||
}
|
||||
|
||||
// Parse remaining templates
|
||||
subject, err := buildTemplate(s.Template.Subject, s)
|
||||
subject, err := ExecuteTemplate(s.Template.Subject, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
@ -104,14 +148,14 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
|||
|
||||
msg.SetHeader("To", s.FormatAddress())
|
||||
if s.Template.Text != "" {
|
||||
text, err := buildTemplate(s.Template.Text, s)
|
||||
text, err := ExecuteTemplate(s.Template.Text, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
msg.SetBody("text/plain", text)
|
||||
}
|
||||
if s.Template.HTML != "" {
|
||||
html, err := buildTemplate(s.Template.HTML, s)
|
||||
html, err := ExecuteTemplate(s.Template.HTML, ptx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
@ -137,6 +181,6 @@ func (s *SendTestEmailRequest) Generate(msg *gomail.Message) error {
|
|||
}
|
||||
|
||||
// GetDialer returns the mailer.Dialer for the underlying SMTP object
|
||||
func (s *SendTestEmailRequest) GetDialer() (mailer.Dialer, error) {
|
||||
func (s *EmailRequest) GetDialer() (mailer.Dialer, error) {
|
||||
return s.SMTP.GetDialer()
|
||||
}
|
||||
|
|
|
@ -11,14 +11,16 @@ import (
|
|||
)
|
||||
|
||||
func (s *ModelsSuite) TestEmailNotPresent(ch *check.C) {
|
||||
req := &SendTestEmailRequest{}
|
||||
req := &EmailRequest{}
|
||||
ch.Assert(req.Validate(), check.Equals, ErrEmailNotSpecified)
|
||||
req.Email = "test@example.com"
|
||||
ch.Assert(req.Validate(), check.Equals, ErrFromAddressNotSpecified)
|
||||
req.FromAddress = "from@example.com"
|
||||
ch.Assert(req.Validate(), check.Equals, nil)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestEmailRequestBackoff(ch *check.C) {
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
ErrorChan: make(chan error),
|
||||
}
|
||||
expected := errors.New("Temporary Error")
|
||||
|
@ -30,7 +32,7 @@ func (s *ModelsSuite) TestEmailRequestBackoff(ch *check.C) {
|
|||
}
|
||||
|
||||
func (s *ModelsSuite) TestEmailRequestError(ch *check.C) {
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
ErrorChan: make(chan error),
|
||||
}
|
||||
expected := errors.New("Temporary Error")
|
||||
|
@ -42,7 +44,7 @@ func (s *ModelsSuite) TestEmailRequestError(ch *check.C) {
|
|||
}
|
||||
|
||||
func (s *ModelsSuite) TestEmailRequestSuccess(ch *check.C) {
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
ErrorChan: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
|
@ -62,15 +64,15 @@ func (s *ModelsSuite) TestEmailRequestGenerate(ch *check.C) {
|
|||
Text: "{{.Email}} - Text",
|
||||
HTML: "{{.Email}} - HTML",
|
||||
}
|
||||
target := Target{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
}
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
SMTP: smtp,
|
||||
Template: template,
|
||||
Target: target,
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
},
|
||||
FromAddress: smtp.FromAddress,
|
||||
}
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
|
@ -104,23 +106,24 @@ func (s *ModelsSuite) TestEmailRequestURLTemplating(ch *check.C) {
|
|||
Text: "{{.URL}}",
|
||||
HTML: "{{.URL}}",
|
||||
}
|
||||
target := Target{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
}
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
SMTP: smtp,
|
||||
Template: template,
|
||||
Target: target,
|
||||
URL: "http://127.0.0.1/{{.Email}}",
|
||||
URL: "http://127.0.0.1/{{.Email}}",
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
},
|
||||
FromAddress: smtp.FromAddress,
|
||||
RId: fmt.Sprintf("%s-foobar", PreviewPrefix),
|
||||
}
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
err = req.Generate(msg)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
expectedURL := fmt.Sprintf("http://127.0.0.1/%s", target.Email)
|
||||
expectedURL := fmt.Sprintf("http://127.0.0.1/%s?%s=%s", req.Email, RecipientParameter, req.RId)
|
||||
|
||||
msgBuff := &bytes.Buffer{}
|
||||
_, err = msg.WriteTo(msgBuff)
|
||||
|
@ -142,15 +145,15 @@ func (s *ModelsSuite) TestEmailRequestGenerateEmptySubject(ch *check.C) {
|
|||
Text: "{{.Email}} - Text",
|
||||
HTML: "{{.Email}} - HTML",
|
||||
}
|
||||
target := Target{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
}
|
||||
req := &SendTestEmailRequest{
|
||||
req := &EmailRequest{
|
||||
SMTP: smtp,
|
||||
Template: template,
|
||||
Target: target,
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
},
|
||||
FromAddress: smtp.FromAddress,
|
||||
}
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
|
@ -171,3 +174,44 @@ func (s *ModelsSuite) TestEmailRequestGenerateEmptySubject(ch *check.C) {
|
|||
ch.Assert(err, check.Equals, nil)
|
||||
ch.Assert(got.Subject, check.Equals, expected.Subject)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostSendTestEmailRequest(ch *check.C) {
|
||||
smtp := SMTP{
|
||||
FromAddress: "from@example.com",
|
||||
}
|
||||
template := Template{
|
||||
Name: "Test Template",
|
||||
Subject: "",
|
||||
Text: "{{.Email}} - Text",
|
||||
HTML: "{{.Email}} - HTML",
|
||||
UserId: 1,
|
||||
}
|
||||
err := PostTemplate(&template)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
page := Page{
|
||||
Name: "Test Page",
|
||||
HTML: "test",
|
||||
UserId: 1,
|
||||
}
|
||||
err = PostPage(&page)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
req := &EmailRequest{
|
||||
SMTP: smtp,
|
||||
TemplateId: template.Id,
|
||||
PageId: page.Id,
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "First",
|
||||
LastName: "Last",
|
||||
Email: "firstlast@example.com",
|
||||
},
|
||||
}
|
||||
err = PostEmailRequest(req)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
||||
got, err := GetEmailRequestByResultId(req.RId)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
ch.Assert(got.RId, check.Equals, req.RId)
|
||||
ch.Assert(got.Email, check.Equals, req.Email)
|
||||
}
|
||||
|
|
|
@ -46,14 +46,33 @@ type GroupTarget struct {
|
|||
// Target contains the fields needed for individual targets specified by the user
|
||||
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
|
||||
type Target struct {
|
||||
Id int64 `json:"-"`
|
||||
Id int64 `json:"-"`
|
||||
BaseRecipient
|
||||
}
|
||||
|
||||
// BaseRecipient contains the fields for a single recipient. This is the base
|
||||
// struct used in members of groups and campaign results.
|
||||
type BaseRecipient struct {
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Position string `json:"position"`
|
||||
}
|
||||
|
||||
// Returns the email address to use in the "To" header of the email
|
||||
// FormatAddress returns the email address to use in the "To" header of the email
|
||||
func (r *BaseRecipient) FormatAddress() string {
|
||||
addr := r.Email
|
||||
if r.FirstName != "" && r.LastName != "" {
|
||||
a := &mail.Address{
|
||||
Name: fmt.Sprintf("%s %s", r.FirstName, r.LastName),
|
||||
Address: r.Email,
|
||||
}
|
||||
addr = a.String()
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// FormatAddress returns the email address to use in the "To" header of the email
|
||||
func (t *Target) FormatAddress() string {
|
||||
addr := t.Email
|
||||
if t.FirstName != "" && t.LastName != "" {
|
||||
|
@ -66,7 +85,7 @@ func (t *Target) FormatAddress() string {
|
|||
return addr
|
||||
}
|
||||
|
||||
// ErrNoEmailSpecified is thrown when no email is specified for the Target
|
||||
// ErrEmailNotSpecified is thrown when no email is specified for the Target
|
||||
var ErrEmailNotSpecified = errors.New("No email address specified")
|
||||
|
||||
// ErrGroupNameNotSpecified is thrown when a group name is not specified
|
||||
|
@ -274,11 +293,12 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
|||
return err
|
||||
}
|
||||
trans := db.Begin()
|
||||
trans.Where(t).FirstOrCreate(&t)
|
||||
err = trans.Where(t).FirstOrCreate(&t).Error
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"email": t.Email,
|
||||
}).Error("Error adding target")
|
||||
}).Error(err)
|
||||
trans.Rollback()
|
||||
return err
|
||||
}
|
||||
err = trans.Where("group_id=? and target_id=?", gid, t.Id).Find(&GroupTarget{}).Error
|
||||
|
@ -286,6 +306,7 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
|||
err = trans.Save(&GroupTarget{GroupId: gid, TargetId: t.Id}).Error
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
trans.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -293,10 +314,12 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
|||
log.WithFields(logrus.Fields{
|
||||
"email": t.Email,
|
||||
}).Error("Error adding many-many mapping")
|
||||
trans.Rollback()
|
||||
return err
|
||||
}
|
||||
err = trans.Commit().Error
|
||||
if err != nil {
|
||||
trans.Rollback()
|
||||
log.Error("Error committing db changes")
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
func (s *ModelsSuite) TestPostGroup(c *check.C) {
|
||||
g := Group{Name: "Test Group"}
|
||||
g.Targets = []Target{Target{Email: "test@example.com"}}
|
||||
g.Targets = []Target{Target{BaseRecipient: BaseRecipient{Email: "test@example.com"}}}
|
||||
g.UserId = 1
|
||||
err := PostGroup(&g)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
|
@ -17,7 +17,7 @@ func (s *ModelsSuite) TestPostGroup(c *check.C) {
|
|||
|
||||
func (s *ModelsSuite) TestPostGroupNoName(c *check.C) {
|
||||
g := Group{Name: ""}
|
||||
g.Targets = []Target{Target{Email: "test@example.com"}}
|
||||
g.Targets = []Target{Target{BaseRecipient: BaseRecipient{Email: "test@example.com"}}}
|
||||
g.UserId = 1
|
||||
err := PostGroup(&g)
|
||||
c.Assert(err, check.Equals, ErrGroupNameNotSpecified)
|
||||
|
@ -34,14 +34,22 @@ func (s *ModelsSuite) TestPostGroupNoTargets(c *check.C) {
|
|||
func (s *ModelsSuite) TestGetGroups(c *check.C) {
|
||||
// Add groups.
|
||||
PostGroup(&Group{
|
||||
Name: "Test Group 1",
|
||||
Targets: []Target{Target{Email: "test1@example.com"}},
|
||||
UserId: 1,
|
||||
Name: "Test Group 1",
|
||||
Targets: []Target{
|
||||
Target{
|
||||
BaseRecipient: BaseRecipient{Email: "test1@example.com"},
|
||||
},
|
||||
},
|
||||
UserId: 1,
|
||||
})
|
||||
PostGroup(&Group{
|
||||
Name: "Test Group 2",
|
||||
Targets: []Target{Target{Email: "test2@example.com"}},
|
||||
UserId: 1,
|
||||
Name: "Test Group 2",
|
||||
Targets: []Target{
|
||||
Target{
|
||||
BaseRecipient: BaseRecipient{Email: "test2@example.com"},
|
||||
},
|
||||
},
|
||||
UserId: 1,
|
||||
})
|
||||
|
||||
// Get groups and test result.
|
||||
|
@ -65,9 +73,13 @@ func (s *ModelsSuite) TestGetGroupsNoGroups(c *check.C) {
|
|||
func (s *ModelsSuite) TestGetGroup(c *check.C) {
|
||||
// Add group.
|
||||
originalGroup := &Group{
|
||||
Name: "Test Group",
|
||||
Targets: []Target{Target{Email: "test@example.com"}},
|
||||
UserId: 1,
|
||||
Name: "Test Group",
|
||||
Targets: []Target{
|
||||
Target{
|
||||
BaseRecipient: BaseRecipient{Email: "test@example.com"},
|
||||
},
|
||||
},
|
||||
UserId: 1,
|
||||
}
|
||||
c.Assert(PostGroup(originalGroup), check.Equals, nil)
|
||||
|
||||
|
@ -87,9 +99,13 @@ func (s *ModelsSuite) TestGetGroupNoGroups(c *check.C) {
|
|||
func (s *ModelsSuite) TestGetGroupByName(c *check.C) {
|
||||
// Add group.
|
||||
PostGroup(&Group{
|
||||
Name: "Test Group",
|
||||
Targets: []Target{Target{Email: "test@example.com"}},
|
||||
UserId: 1,
|
||||
Name: "Test Group",
|
||||
Targets: []Target{
|
||||
Target{
|
||||
BaseRecipient: BaseRecipient{Email: "test@example.com"},
|
||||
},
|
||||
},
|
||||
UserId: 1,
|
||||
})
|
||||
|
||||
// Get group and test result.
|
||||
|
@ -109,8 +125,8 @@ func (s *ModelsSuite) TestPutGroup(c *check.C) {
|
|||
// Add test group.
|
||||
group := Group{Name: "Test Group"}
|
||||
group.Targets = []Target{
|
||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
PostGroup(&group)
|
||||
|
@ -134,8 +150,8 @@ func (s *ModelsSuite) TestPutGroupEmptyAttribute(c *check.C) {
|
|||
// Add test group.
|
||||
group := Group{Name: "Test Group"}
|
||||
group.Targets = []Target{
|
||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
PostGroup(&group)
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/gophish/gomail"
|
||||
|
@ -136,18 +132,6 @@ func (m *MailLog) GetDialer() (mailer.Dialer, error) {
|
|||
return c.SMTP.GetDialer()
|
||||
}
|
||||
|
||||
// buildTemplate creates a templated string based on the provided
|
||||
// template body and data.
|
||||
func buildTemplate(text string, data interface{}) (string, error) {
|
||||
buff := bytes.Buffer{}
|
||||
tmpl, err := template.New("template").Parse(text)
|
||||
if err != nil {
|
||||
return buff.String(), err
|
||||
}
|
||||
err = tmpl.Execute(&buff, data)
|
||||
return buff.String(), err
|
||||
}
|
||||
|
||||
// Generate fills in the details of a gomail.Message instance with
|
||||
// the correct headers and body from the campaign and recipient listed in
|
||||
// the maillog. We accept the gomail.Message as an argument so that the caller
|
||||
|
@ -161,51 +145,26 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := mail.ParseAddress(c.SMTP.FromAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fn := f.Name
|
||||
if fn == "" {
|
||||
fn = f.Address
|
||||
}
|
||||
msg.SetAddressHeader("From", f.Address, f.Name)
|
||||
campaignURL, err := buildTemplate(c.URL, r)
|
||||
|
||||
ptx, err := NewPhishingTemplateContext(&c, r.BaseRecipient, r.RId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
phishURL, _ := url.Parse(campaignURL)
|
||||
q := phishURL.Query()
|
||||
q.Set("rid", r.RId)
|
||||
phishURL.RawQuery = q.Encode()
|
||||
|
||||
trackingURL, _ := url.Parse(campaignURL)
|
||||
trackingURL.Path = path.Join(trackingURL.Path, "/track")
|
||||
trackingURL.RawQuery = q.Encode()
|
||||
|
||||
td := struct {
|
||||
Result
|
||||
URL string
|
||||
TrackingURL string
|
||||
Tracker string
|
||||
From string
|
||||
}{
|
||||
r,
|
||||
phishURL.String(),
|
||||
trackingURL.String(),
|
||||
"<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
|
||||
fn,
|
||||
}
|
||||
|
||||
// Parse the customHeader templates
|
||||
for _, header := range c.SMTP.Headers {
|
||||
key, err := buildTemplate(header.Key, td)
|
||||
key, err := ExecuteTemplate(header.Key, ptx)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
value, err := buildTemplate(header.Value, td)
|
||||
value, err := ExecuteTemplate(header.Value, ptx)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
@ -215,7 +174,7 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
}
|
||||
|
||||
// Parse remaining templates
|
||||
subject, err := buildTemplate(c.Template.Subject, td)
|
||||
subject, err := ExecuteTemplate(c.Template.Subject, ptx)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
@ -226,14 +185,14 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
|
|||
|
||||
msg.SetHeader("To", r.FormatAddress())
|
||||
if c.Template.Text != "" {
|
||||
text, err := buildTemplate(c.Template.Text, td)
|
||||
text, err := ExecuteTemplate(c.Template.Text, ptx)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
msg.SetBody("text/plain", text)
|
||||
}
|
||||
if c.Template.HTML != "" {
|
||||
html, err := buildTemplate(c.Template.HTML, td)
|
||||
html, err := ExecuteTemplate(c.Template.HTML, ptx)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ func (s *ModelsSuite) createCampaignDependencies(ch *check.C, optional ...string
|
|||
// we use the optional parameter to pass an alternative subject
|
||||
group := Group{Name: "Test Group"}
|
||||
group.Targets = []Target{
|
||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
ch.Assert(PostGroup(&group), check.Equals, nil)
|
||||
|
|
|
@ -3,10 +3,8 @@ package models
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
log "github.com/gophish/gophish/logger"
|
||||
|
@ -30,10 +28,6 @@ type Result struct {
|
|||
CampaignId int64 `json:"-"`
|
||||
UserId int64 `json:"-"`
|
||||
RId string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Position string `json:"position"`
|
||||
Status string `json:"status" sql:"not null"`
|
||||
IP string `json:"ip"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
|
@ -41,6 +35,7 @@ type Result struct {
|
|||
SendDate time.Time `json:"send_date"`
|
||||
Reported bool `json:"reported" sql:"not null"`
|
||||
ModifiedDate time.Time `json:"modified_date"`
|
||||
BaseRecipient
|
||||
}
|
||||
|
||||
func (r *Result) createEvent(status string, details interface{}) (*Event, error) {
|
||||
|
@ -178,22 +173,30 @@ func (r *Result) UpdateGeo(addr string) error {
|
|||
return db.Save(r).Error
|
||||
}
|
||||
|
||||
func generateResultId() (string, error) {
|
||||
const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
k := make([]byte, 7)
|
||||
for i := range k {
|
||||
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
k[i] = alphaNum[idx.Int64()]
|
||||
}
|
||||
return string(k), nil
|
||||
}
|
||||
|
||||
// GenerateId generates a unique key to represent the result
|
||||
// in the database
|
||||
func (r *Result) GenerateId() error {
|
||||
// Keep trying until we generate a unique key (shouldn't take more than one or two iterations)
|
||||
const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
k := make([]byte, 7)
|
||||
for {
|
||||
for i := range k {
|
||||
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k[i] = alphaNum[idx.Int64()]
|
||||
rid, err := generateResultId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.RId = string(k)
|
||||
err := db.Table("results").Where("r_id=?", r.RId).First(&Result{}).Error
|
||||
r.RId = rid
|
||||
err = db.Table("results").Where("r_id=?", r.RId).First(&Result{}).Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
break
|
||||
}
|
||||
|
@ -201,19 +204,6 @@ func (r *Result) GenerateId() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FormatAddress returns the email address to use in the "To" header of the email
|
||||
func (r *Result) FormatAddress() string {
|
||||
addr := r.Email
|
||||
if r.FirstName != "" && r.LastName != "" {
|
||||
a := &mail.Address{
|
||||
Name: fmt.Sprintf("%s %s", r.FirstName, r.LastName),
|
||||
Address: r.Email,
|
||||
}
|
||||
addr = a.String()
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// GetResult returns the Result object from the database
|
||||
// given the ResultId
|
||||
func GetResult(rid string) (Result, error) {
|
||||
|
|
|
@ -18,9 +18,11 @@ func (s *ModelsSuite) TestGenerateResultId(c *check.C) {
|
|||
|
||||
func (s *ModelsSuite) TestFormatAddress(c *check.C) {
|
||||
r := Result{
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "johndoe@example.com",
|
||||
BaseRecipient: BaseRecipient{
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
}
|
||||
expected := &mail.Address{
|
||||
Name: "John Doe",
|
||||
|
@ -29,7 +31,7 @@ func (s *ModelsSuite) TestFormatAddress(c *check.C) {
|
|||
c.Assert(r.FormatAddress(), check.Equals, expected.String())
|
||||
|
||||
r = Result{
|
||||
Email: "johndoe@example.com",
|
||||
BaseRecipient: BaseRecipient{Email: "johndoe@example.com"},
|
||||
}
|
||||
c.Assert(r.FormatAddress(), check.Equals, r.Email)
|
||||
}
|
||||
|
@ -59,9 +61,9 @@ func (s *ModelsSuite) TestResultScheduledStatus(ch *check.C) {
|
|||
func (s *ModelsSuite) TestDuplicateResults(ch *check.C) {
|
||||
group := Group{Name: "Test Group"}
|
||||
group.Targets = []Target{
|
||||
Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
Target{Email: "test1@example.com", FirstName: "Duplicate", LastName: "Duplicate"},
|
||||
Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test1@example.com", FirstName: "Duplicate", LastName: "Duplicate"}},
|
||||
Target{BaseRecipient: BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
ch.Assert(PostGroup(&group), check.Equals, nil)
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
log "github.com/gophish/gophish/logger"
|
||||
|
@ -36,7 +34,6 @@ func (t *Template) Validate() error {
|
|||
case t.Text == "" && t.HTML == "":
|
||||
return ErrTemplateMissingParameter
|
||||
}
|
||||
var buff bytes.Buffer
|
||||
// Test that the variables used in the template
|
||||
// validate with no issues
|
||||
td := struct {
|
||||
|
@ -47,31 +44,27 @@ func (t *Template) Validate() error {
|
|||
From string
|
||||
}{
|
||||
Result{
|
||||
Email: "foo@bar.com",
|
||||
FirstName: "Foo",
|
||||
LastName: "Bar",
|
||||
Position: "Test",
|
||||
BaseRecipient: BaseRecipient{
|
||||
Email: "foo@bar.com",
|
||||
FirstName: "Foo",
|
||||
LastName: "Bar",
|
||||
Position: "Test",
|
||||
},
|
||||
},
|
||||
"http://foo.bar",
|
||||
"http://foo.bar/track",
|
||||
"<img src='http://foo.bar/track",
|
||||
"John Doe <foo@bar.com>",
|
||||
}
|
||||
tmpl, err := template.New("html_template").Parse(t.HTML)
|
||||
_, err := ExecuteTemplate(t.HTML, td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpl.Execute(&buff, td)
|
||||
_, err = ExecuteTemplate(t.Text, td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err = template.New("text_template").Parse(t.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tmpl.Execute(&buff, td)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTemplates returns the templates owned by the given user.
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"path"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// TemplateContext is an interface that allows both campaigns and email
|
||||
// requests to have a PhishingTemplateContext generated for them.
|
||||
type TemplateContext interface {
|
||||
getFromAddress() string
|
||||
getBaseURL() string
|
||||
}
|
||||
|
||||
// PhishingTemplateContext is the context that is sent to any template, such
|
||||
// as the email or landing page content.
|
||||
type PhishingTemplateContext struct {
|
||||
From string
|
||||
URL string
|
||||
Tracker string
|
||||
TrackingURL string
|
||||
RId string
|
||||
BaseRecipient
|
||||
}
|
||||
|
||||
// NewPhishingTemplateContext returns a populated PhishingTemplateContext,
|
||||
// parsing the correct fields from the provided TemplateContext and recipient.
|
||||
func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string) (PhishingTemplateContext, error) {
|
||||
f, err := mail.ParseAddress(ctx.getFromAddress())
|
||||
if err != nil {
|
||||
return PhishingTemplateContext{}, err
|
||||
}
|
||||
fn := f.Name
|
||||
if fn == "" {
|
||||
fn = f.Address
|
||||
}
|
||||
baseURL, err := ExecuteTemplate(ctx.getBaseURL(), r)
|
||||
if err != nil {
|
||||
return PhishingTemplateContext{}, err
|
||||
}
|
||||
|
||||
phishURL, _ := url.Parse(baseURL)
|
||||
q := phishURL.Query()
|
||||
q.Set(RecipientParameter, rid)
|
||||
phishURL.RawQuery = q.Encode()
|
||||
|
||||
trackingURL, _ := url.Parse(baseURL)
|
||||
trackingURL.Path = path.Join(trackingURL.Path, "/track")
|
||||
trackingURL.RawQuery = q.Encode()
|
||||
|
||||
return PhishingTemplateContext{
|
||||
BaseRecipient: r,
|
||||
URL: phishURL.String(),
|
||||
TrackingURL: trackingURL.String(),
|
||||
Tracker: "<img alt='' style='display: none' src='" + trackingURL.String() + "'/>",
|
||||
From: fn,
|
||||
RId: rid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteTemplate creates a templated string based on the provided
|
||||
// template body and data.
|
||||
func ExecuteTemplate(text string, data interface{}) (string, error) {
|
||||
buff := bytes.Buffer{}
|
||||
tmpl, err := template.New("template").Parse(text)
|
||||
if err != nil {
|
||||
return buff.String(), err
|
||||
}
|
||||
err = tmpl.Execute(&buff, data)
|
||||
return buff.String(), err
|
||||
}
|
10
util/util.go
10
util/util.go
|
@ -100,10 +100,12 @@ func ParseCSV(r *http.Request) ([]models.Target, error) {
|
|||
ps = record[pi]
|
||||
}
|
||||
t := models.Target{
|
||||
FirstName: fn,
|
||||
LastName: ln,
|
||||
Email: ea,
|
||||
Position: ps,
|
||||
BaseRecipient: models.BaseRecipient{
|
||||
FirstName: fn,
|
||||
LastName: ln,
|
||||
Email: ea,
|
||||
Position: ps,
|
||||
},
|
||||
}
|
||||
ts = append(ts, t)
|
||||
}
|
||||
|
|
|
@ -52,9 +52,11 @@ func buildCSVRequest(csvPayload string) (*http.Request, error) {
|
|||
|
||||
func (s *UtilSuite) TestParseCSVEmail() {
|
||||
expected := models.Target{
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "johndoe@example.com",
|
||||
BaseRecipient: models.BaseRecipient{
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "johndoe@example.com",
|
||||
},
|
||||
}
|
||||
|
||||
csvPayload := fmt.Sprintf("%s,%s,<%s>", expected.FirstName, expected.LastName, expected.Email)
|
||||
|
|
|
@ -86,7 +86,7 @@ func (w *Worker) LaunchCampaign(c models.Campaign) {
|
|||
}
|
||||
|
||||
// SendTestEmail sends a test email
|
||||
func (w *Worker) SendTestEmail(s *models.SendTestEmailRequest) error {
|
||||
func (w *Worker) SendTestEmail(s *models.EmailRequest) error {
|
||||
go func() {
|
||||
mailer.Mailer.Queue <- []mailer.Mail{s}
|
||||
}()
|
||||
|
|
|
@ -35,8 +35,8 @@ func (s *WorkerSuite) SetupTest() {
|
|||
// Add a group
|
||||
group := models.Group{Name: "Test Group"}
|
||||
group.Targets = []models.Target{
|
||||
models.Target{Email: "test1@example.com", FirstName: "First", LastName: "Example"},
|
||||
models.Target{Email: "test2@example.com", FirstName: "Second", LastName: "Example"},
|
||||
models.Target{BaseRecipient: models.BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||
models.Target{BaseRecipient: models.BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||
}
|
||||
group.UserId = 1
|
||||
models.PostGroup(&group)
|
||||
|
|
Loading…
Reference in New Issue