mirror of https://github.com/gophish/gophish
Merge pull request #169 from gophish/78-store-smtp-settings
Added storage of SMTP settings to database. Closes #78pull/186/head
commit
af91483cd4
|
@ -35,7 +35,7 @@ func API(w http.ResponseWriter, r *http.Request) {
|
|||
templates := template.New("template")
|
||||
_, err := templates.ParseFiles("templates/docs.html")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Logger.Println(err)
|
||||
}
|
||||
template.Must(templates, err).ExecuteTemplate(w, "base", nil)
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
|
|||
case r.Method == "GET":
|
||||
cs, err := models.GetCampaigns(ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Logger.Println(err)
|
||||
}
|
||||
JSONResponse(w, cs, http.StatusOK)
|
||||
//POST: Create a new campaign and return it as JSON
|
||||
|
@ -146,8 +146,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// API_Groups_Id returns details about the requested campaign. If the campaign is not
|
||||
// valid, API_Campaigns_Id returns null.
|
||||
// API_Groups_Id returns details about the requested campaign. If the group is not
|
||||
// valid, API_Groups_Id returns null.
|
||||
func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||
|
@ -191,7 +191,7 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
|
|||
case r.Method == "GET":
|
||||
ts, err := models.GetTemplates(ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Logger.Println(err)
|
||||
}
|
||||
JSONResponse(w, ts, http.StatusOK)
|
||||
//POST: Create a new template and return it as JSON
|
||||
|
@ -274,7 +274,7 @@ func API_Pages(w http.ResponseWriter, r *http.Request) {
|
|||
case r.Method == "GET":
|
||||
ps, err := models.GetPages(ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
Logger.Println(err)
|
||||
}
|
||||
JSONResponse(w, ps, http.StatusOK)
|
||||
//POST: Create a new page and return it as JSON
|
||||
|
@ -345,6 +345,88 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// API_SMTP handles requests for the /api/smtp/ endpoint
|
||||
func API_SMTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == "GET":
|
||||
ss, err := models.GetSMTPs(ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
JSONResponse(w, ss, http.StatusOK)
|
||||
//POST: Create a new SMTP and return it as JSON
|
||||
case r.Method == "POST":
|
||||
s := models.SMTP{}
|
||||
// Put the request into a page
|
||||
err := json.NewDecoder(r.Body).Decode(&s)
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid request"}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Check to make sure the name is unique
|
||||
_, err = models.GetSMTPByName(s.Name, ctx.Get(r, "user_id").(int64))
|
||||
if err != gorm.RecordNotFound {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "SMTP name already in use"}, http.StatusConflict)
|
||||
Logger.Println(err)
|
||||
return
|
||||
}
|
||||
s.ModifiedDate = time.Now()
|
||||
s.UserId = ctx.Get(r, "user_id").(int64)
|
||||
err = models.PostSMTP(&s)
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
JSONResponse(w, s, http.StatusCreated)
|
||||
}
|
||||
}
|
||||
|
||||
// API_SMTP_Id contains functions to handle the GET'ing, DELETE'ing, and PUT'ing
|
||||
// of a SMTP object
|
||||
func API_SMTP_Id(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||
s, err := models.GetSMTP(id, ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "SMTP not found"}, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case r.Method == "GET":
|
||||
JSONResponse(w, s, http.StatusOK)
|
||||
case r.Method == "DELETE":
|
||||
err = models.DeleteSMTP(id, ctx.Get(r, "user_id").(int64))
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "Error deleting SMTP"}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
JSONResponse(w, models.Response{Success: true, Message: "SMTP Deleted Successfully"}, http.StatusOK)
|
||||
case r.Method == "PUT":
|
||||
s = models.SMTP{}
|
||||
err = json.NewDecoder(r.Body).Decode(&s)
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
if s.Id != id {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "/:id and /:smtp_id mismatch"}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = s.Validate()
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid attributes given"}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.ModifiedDate = time.Now()
|
||||
s.UserId = ctx.Get(r, "user_id").(int64)
|
||||
err = models.PutSMTP(&s)
|
||||
if err != nil {
|
||||
JSONResponse(w, models.Response{Success: false, Message: "Error updating page"}, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
JSONResponse(w, s, http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
// API_Import_Group imports a CSV of group members
|
||||
func API_Import_Group(w http.ResponseWriter, r *http.Request) {
|
||||
ts, err := util.ParseCSV(r)
|
||||
|
@ -438,16 +520,50 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
|
|||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// Get the template requested by name
|
||||
s.Template, err = models.GetTemplateByName(s.Template.Name, ctx.Get(r, "user_id").(int64))
|
||||
if err == gorm.RecordNotFound {
|
||||
Logger.Printf("Error - Template %s does not exist", s.Template.Name)
|
||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrTemplateNotFound.Error()}, http.StatusBadRequest)
|
||||
} else if err != nil {
|
||||
Logger.Println(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
|
||||
// If a Template is not specified use a default
|
||||
if s.Template.Name == "" {
|
||||
//default message body
|
||||
text := "It works!\n\nThis is an email letting you know that your gophish\nconfiguration was successful.\n" +
|
||||
"Here are the details:\n\nWho you sent from: {{.From}}\n\nWho you sent to: \n" +
|
||||
"{{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))
|
||||
if err == gorm.RecordNotFound {
|
||||
Logger.Printf("Error - Template %s does not exist", s.Template.Name)
|
||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrTemplateNotFound.Error()}, http.StatusBadRequest)
|
||||
} else if err != nil {
|
||||
Logger.Println(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If a complete sending profile is provided use it
|
||||
if err := s.SMTP.Validate(); err != nil {
|
||||
// Otherwise get the SMTP requested by name
|
||||
s.SMTP, err = models.GetSMTPByName(s.SMTP.Name, ctx.Get(r, "user_id").(int64))
|
||||
if err == gorm.RecordNotFound {
|
||||
Logger.Printf("Error - Sending profile %s does not exist", s.SMTP.Name)
|
||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrSMTPNotFound.Error()}, http.StatusBadRequest)
|
||||
} else if err != nil {
|
||||
Logger.Println(err)
|
||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Send the test email
|
||||
err = worker.SendTestEmail(s)
|
||||
if err != nil {
|
||||
|
|
|
@ -35,6 +35,7 @@ func CreateAdminRouter() http.Handler {
|
|||
router.HandleFunc("/templates", Use(Templates, mid.RequireLogin))
|
||||
router.HandleFunc("/users", Use(Users, mid.RequireLogin))
|
||||
router.HandleFunc("/landing_pages", Use(LandingPages, mid.RequireLogin))
|
||||
router.HandleFunc("/sending_profiles", Use(SendingProfiles, mid.RequireLogin))
|
||||
router.HandleFunc("/register", Use(Register, mid.RequireLogin))
|
||||
router.HandleFunc("/settings", Use(Settings, mid.RequireLogin))
|
||||
// Create the API routes
|
||||
|
@ -50,6 +51,8 @@ func CreateAdminRouter() http.Handler {
|
|||
api.HandleFunc("/templates/{id:[0-9]+}", Use(API_Templates_Id, mid.RequireAPIKey))
|
||||
api.HandleFunc("/pages/", Use(API_Pages, mid.RequireAPIKey))
|
||||
api.HandleFunc("/pages/{id:[0-9]+}", Use(API_Pages_Id, mid.RequireAPIKey))
|
||||
api.HandleFunc("/smtp/", Use(API_SMTP, mid.RequireAPIKey))
|
||||
api.HandleFunc("/smtp/{id:[0-9]+}", Use(API_SMTP_Id, mid.RequireAPIKey))
|
||||
api.HandleFunc("/util/send_test_email", Use(API_Send_Test_Email, mid.RequireAPIKey))
|
||||
api.HandleFunc("/import/group", API_Import_Group)
|
||||
api.HandleFunc("/import/email", API_Import_Email)
|
||||
|
@ -69,6 +72,8 @@ func CreateAdminRouter() http.Handler {
|
|||
csrfHandler.ExemptGlob("/api/templates/*")
|
||||
csrfHandler.ExemptGlob("/api/pages")
|
||||
csrfHandler.ExemptGlob("/api/pages/*")
|
||||
csrfHandler.ExemptGlob("/api/smtp")
|
||||
csrfHandler.ExemptGlob("/api/smtp/*")
|
||||
csrfHandler.ExemptGlob("/api/import/*")
|
||||
csrfHandler.ExemptGlob("/api/util/*")
|
||||
csrfHandler.ExemptGlob("/static/*")
|
||||
|
@ -269,7 +274,7 @@ func CampaignID(w http.ResponseWriter, r *http.Request) {
|
|||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
}{Title: "Campaign Results", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "campaign_results").ExecuteTemplate(w, "base", params)
|
||||
}
|
||||
|
||||
|
@ -281,7 +286,7 @@ func Templates(w http.ResponseWriter, r *http.Request) {
|
|||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
}{Title: "Email Templates", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "templates").ExecuteTemplate(w, "base", params)
|
||||
}
|
||||
|
||||
|
@ -293,7 +298,7 @@ func Users(w http.ResponseWriter, r *http.Request) {
|
|||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
}{Title: "Users & Groups", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "users").ExecuteTemplate(w, "base", params)
|
||||
}
|
||||
|
||||
|
@ -305,10 +310,22 @@ func LandingPages(w http.ResponseWriter, r *http.Request) {
|
|||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
}{Title: "Landing Pages", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "landing_pages").ExecuteTemplate(w, "base", params)
|
||||
}
|
||||
|
||||
// SendingProfiles handles the default path and template execution
|
||||
func SendingProfiles(w http.ResponseWriter, r *http.Request) {
|
||||
// Example of using session - will be removed.
|
||||
params := struct {
|
||||
User models.User
|
||||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Sending Profiles", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "sending_profiles").ExecuteTemplate(w, "base", params)
|
||||
}
|
||||
|
||||
// Settings handles the changing of settings
|
||||
func Settings(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
|
@ -318,7 +335,7 @@ func Settings(w http.ResponseWriter, r *http.Request) {
|
|||
Title string
|
||||
Flashes []interface{}
|
||||
Token string
|
||||
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
}{Title: "Settings", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)}
|
||||
getTemplate(w, "settings").ExecuteTemplate(w, "base", params)
|
||||
case r.Method == "POST":
|
||||
err := auth.ChangePassword(r)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
-- Move the relationship between campaigns and smtp to campaigns
|
||||
ALTER TABLE campaigns ADD COLUMN "smtp_id" bigint;
|
||||
-- Create a new table to store smtp records
|
||||
DROP TABLE smtp;
|
||||
CREATE TABLE smtp(
|
||||
id integer primary key autoincrement,
|
||||
user_id bigint,
|
||||
interface_type varchar(255),
|
||||
name varchar(255),
|
||||
host varchar(255),
|
||||
username varchar(255),
|
||||
password varchar(255),
|
||||
from_address varchar(255),
|
||||
modified_date datetime default CURRENT_TIMESTAMP,
|
||||
ignore_cert_errors BOOLEAN
|
||||
);
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
|
@ -23,6 +23,7 @@ type Campaign struct {
|
|||
Results []Result `json:"results,omitempty"`
|
||||
Groups []Group `json:"groups,omitempty"`
|
||||
Events []Event `json:"timeline,omitemtpy"`
|
||||
SMTPId int64 `json:"-"`
|
||||
SMTP SMTP `json:"smtp"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
@ -39,6 +40,9 @@ var ErrTemplateNotSpecified = errors.New("No email template specified")
|
|||
// ErrPageNotSpecified indicates a landing page was not provided for the campaign
|
||||
var ErrPageNotSpecified = errors.New("No landing page specified")
|
||||
|
||||
// ErrSMTPNotSpecified indicates a sending profile was not provided for the campaign
|
||||
var ErrSMTPNotSpecified = errors.New("No sending profile specified")
|
||||
|
||||
// ErrTemplateNotFound indicates the template specified does not exist in the database
|
||||
var ErrTemplateNotFound = errors.New("Template not found")
|
||||
|
||||
|
@ -48,6 +52,9 @@ var ErrGroupNotFound = errors.New("Group not found")
|
|||
// ErrPageNotFound indicates a page specified by the user does not exist in the database
|
||||
var ErrPageNotFound = errors.New("Page not found")
|
||||
|
||||
// ErrSMTPNotFound indicates a sending profile specified by the user does not exist in the database
|
||||
var ErrSMTPNotFound = errors.New("Sending profile not found")
|
||||
|
||||
// Validate checks to make sure there are no invalid fields in a submitted campaign
|
||||
func (c *Campaign) Validate() error {
|
||||
switch {
|
||||
|
@ -59,8 +66,10 @@ func (c *Campaign) Validate() error {
|
|||
return ErrTemplateNotSpecified
|
||||
case c.Page.Name == "":
|
||||
return ErrPageNotSpecified
|
||||
case c.SMTP.Name == "":
|
||||
return ErrSMTPNotSpecified
|
||||
}
|
||||
return c.SMTP.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendTestEmailRequest is the structure of a request
|
||||
|
@ -80,13 +89,10 @@ type SendTestEmailRequest struct {
|
|||
// is valid.
|
||||
func (s *SendTestEmailRequest) Validate() error {
|
||||
switch {
|
||||
case s.Template.Name == "":
|
||||
return ErrTemplateNotSpecified
|
||||
case s.Email == "":
|
||||
return ErrEmailNotSpecified
|
||||
}
|
||||
// Finally, check the SMTP settings
|
||||
return s.SMTP.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStatus changes the campaign status appropriately
|
||||
|
@ -137,6 +143,10 @@ func GetCampaigns(uid int64) ([]Campaign, error) {
|
|||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
err = db.Table("SMTP").Where("id=?", cs[i].SMTPId).Find(&cs[i].SMTP).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
}
|
||||
return cs, err
|
||||
}
|
||||
|
@ -168,6 +178,10 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
|
|||
if err != nil {
|
||||
Logger.Printf("%s: page not found for campaign\n", err)
|
||||
}
|
||||
err = db.Table("SMTP").Where("id=?", c.SMTPId).Find(&c.SMTP).Error
|
||||
if err != nil {
|
||||
Logger.Printf("%s: sending profile not found for campaign\n", err)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
|
@ -214,6 +228,17 @@ func PostCampaign(c *Campaign, uid int64) error {
|
|||
}
|
||||
c.Page = p
|
||||
c.PageId = p.Id
|
||||
// Check to make sure the sending profile exists
|
||||
s, err := GetSMTPByName(c.SMTP.Name, uid)
|
||||
if err == gorm.RecordNotFound {
|
||||
Logger.Printf("Error - Sending profile %s does not exist", s.Name)
|
||||
return ErrPageNotFound
|
||||
} else if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
c.SMTP = s
|
||||
c.SMTPId = s.Id
|
||||
// Insert into the DB
|
||||
err = db.Save(c).Error
|
||||
if err != nil {
|
||||
|
|
|
@ -63,6 +63,40 @@ func (s *ModelsSuite) TestPostGroupNoTargets(c *check.C) {
|
|||
c.Assert(err, check.Equals, ErrNoTargetsSpecified)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostSMTP(c *check.C) {
|
||||
smtp := SMTP{
|
||||
Name: "Test SMTP",
|
||||
Host: "1.1.1.1:25",
|
||||
FromAddress: "Foo Bar <foo@example.com>",
|
||||
UserId: 1,
|
||||
}
|
||||
err = PostSMTP(&smtp)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
ss, err := GetSMTPs(1)
|
||||
c.Assert(err, check.Equals, nil)
|
||||
c.Assert(len(ss), check.Equals, 1)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostSMTPNoHost(c *check.C) {
|
||||
smtp := SMTP{
|
||||
Name: "Test SMTP",
|
||||
FromAddress: "Foo Bar <foo@example.com>",
|
||||
UserId: 1,
|
||||
}
|
||||
err = PostSMTP(&smtp)
|
||||
c.Assert(err, check.Equals, ErrHostNotSpecified)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostSMTPNoFrom(c *check.C) {
|
||||
smtp := SMTP{
|
||||
Name: "Test SMTP",
|
||||
UserId: 1,
|
||||
Host: "1.1.1.1:25",
|
||||
}
|
||||
err = PostSMTP(&smtp)
|
||||
c.Assert(err, check.Equals, ErrFromAddressNotSpecified)
|
||||
}
|
||||
|
||||
func (s *ModelsSuite) TestPostPage(c *check.C) {
|
||||
html := `<html>
|
||||
<head></head>
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
package models
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SMTP contains the attributes needed to handle the sending of campaign emails
|
||||
type SMTP struct {
|
||||
SMTPId int64 `json:"-" gorm:"column:smtp_id; primary_key:yes"`
|
||||
CampaignId int64 `json:"-" gorm:"column:campaign_id"`
|
||||
Host string `json:"host"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" sql:"-"`
|
||||
FromAddress string `json:"from_address"`
|
||||
IgnoreCertErrors bool `json:"ignore_cert_errors"`
|
||||
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
|
||||
UserId int64 `json:"-" gorm:"column:user_id"`
|
||||
Interface string `json:"interface_type" gorm:"column:interface_type"`
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
FromAddress string `json:"from_address"`
|
||||
IgnoreCertErrors bool `json:"ignore_cert_errors"`
|
||||
ModifiedDate time.Time `json:"modified_date"`
|
||||
}
|
||||
|
||||
// ErrFromAddressNotSpecified is thrown when there is no "From" address
|
||||
|
@ -34,5 +41,76 @@ func (s *SMTP) Validate() error {
|
|||
case s.Host == "":
|
||||
return ErrHostNotSpecified
|
||||
}
|
||||
return nil
|
||||
_, err := mail.ParseAddress(s.FromAddress)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSMTPs returns the SMTPs owned by the given user.
|
||||
func GetSMTPs(uid int64) ([]SMTP, error) {
|
||||
ss := []SMTP{}
|
||||
err := db.Where("user_id=?", uid).Find(&ss).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// GetSMTP returns the SMTP, if it exists, specified by the given id and user_id.
|
||||
func GetSMTP(id int64, uid int64) (SMTP, error) {
|
||||
s := SMTP{}
|
||||
err := db.Where("user_id=? and id=?", uid, id).Find(&s).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
// GetSMTPByName returns the SMTP, if it exists, specified by the given name and user_id.
|
||||
func GetSMTPByName(n string, uid int64) (SMTP, error) {
|
||||
s := SMTP{}
|
||||
err := db.Where("user_id=? and name=?", uid, n).Find(&s).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
// PostSMTP creates a new SMTP in the database.
|
||||
func PostSMTP(s *SMTP) error {
|
||||
err := s.Validate()
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
// Insert into the DB
|
||||
err = db.Save(s).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PutSMTP edits an existing SMTP in the database.
|
||||
// Per the PUT Method RFC, it presumes all data for a SMTP is provided.
|
||||
func PutSMTP(s *SMTP) error {
|
||||
err := s.Validate()
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
return err
|
||||
}
|
||||
err = db.Where("id=?", s.Id).Save(s).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteSMTP deletes an existing SMTP in the database.
|
||||
// An error is returned if a SMTP with the given user id and SMTP id is not found.
|
||||
func DeleteSMTP(id int64, uid int64) error {
|
||||
err = db.Where("user_id=?", uid).Delete(SMTP{Id: id}).Error
|
||||
if err != nil {
|
||||
Logger.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -31,11 +31,7 @@ function launch() {
|
|||
name: $("#page").val()
|
||||
},
|
||||
smtp: {
|
||||
from_address: $("input[name=from]").val(),
|
||||
host: $("input[name=host]").val(),
|
||||
username: $("input[name=username]").val(),
|
||||
password: $("input[name=password]").val(),
|
||||
ignore_cert_errors: $("#ignore_cert_errors").prop("checked")
|
||||
name: $("#profile").val()
|
||||
},
|
||||
groups: groups
|
||||
}
|
||||
|
@ -70,11 +66,7 @@ function sendTestEmail() {
|
|||
name: $("#page").val()
|
||||
},
|
||||
smtp: {
|
||||
from_address: $("input[name=from]").val(),
|
||||
host: $("input[name=host]").val(),
|
||||
username: $("input[name=username]").val(),
|
||||
password: $("input[name=password]").val(),
|
||||
ignore_cert_errors: $("#ignore_cert_errors").prop("checked")
|
||||
name: $("#profile").val()
|
||||
}
|
||||
}
|
||||
btnHtml = $("#sendTestModalSubmit").html()
|
||||
|
@ -95,6 +87,12 @@ function sendTestEmail() {
|
|||
|
||||
function dismiss() {
|
||||
$("#modal\\.flashes").empty()
|
||||
$("#name").val("")
|
||||
$("#template").val("")
|
||||
$("#page").val("")
|
||||
$("#url").val("")
|
||||
$("#profile").val("")
|
||||
$("#groupSelect").val("")
|
||||
$("#modal").modal('hide')
|
||||
$("#groupTable").dataTable().DataTable().clear().draw()
|
||||
}
|
||||
|
@ -114,6 +112,7 @@ function edit(campaign) {
|
|||
group_bh.clear();
|
||||
template_bh.clear();
|
||||
page_bh.clear();
|
||||
profile_bh.clear();
|
||||
if (campaign == "new") {
|
||||
api.groups.get()
|
||||
.success(function(groups) {
|
||||
|
@ -142,9 +141,79 @@ function edit(campaign) {
|
|||
page_bh.add(pages)
|
||||
}
|
||||
})
|
||||
api.SMTP.get()
|
||||
.success(function(profiles) {
|
||||
if (profiles.length == 0){
|
||||
modalError("No profiles found!")
|
||||
return false
|
||||
} else {
|
||||
profile_bh.add(profiles)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function copy(idx) {
|
||||
group_bh.clear();
|
||||
template_bh.clear();
|
||||
page_bh.clear();
|
||||
profile_bh.clear();
|
||||
api.groups.get()
|
||||
.success(function(groups) {
|
||||
if (groups.length == 0) {
|
||||
modalError("No groups found!")
|
||||
return false;
|
||||
} else {
|
||||
group_bh.add(groups)
|
||||
}
|
||||
})
|
||||
api.templates.get()
|
||||
.success(function(templates) {
|
||||
if (templates.length == 0) {
|
||||
modalError("No templates found!")
|
||||
return false
|
||||
} else {
|
||||
template_bh.add(templates)
|
||||
}
|
||||
})
|
||||
api.pages.get()
|
||||
.success(function(pages) {
|
||||
if (pages.length == 0) {
|
||||
modalError("No pages found!")
|
||||
return false
|
||||
} else {
|
||||
page_bh.add(pages)
|
||||
}
|
||||
})
|
||||
api.SMTP.get()
|
||||
.success(function(profiles) {
|
||||
if (profiles.length == 0) {
|
||||
modalError("No profiles found!")
|
||||
return false
|
||||
} else {
|
||||
profile_bh.add(profiles)
|
||||
}
|
||||
})
|
||||
// Set our initial values
|
||||
var campaign = campaigns[idx]
|
||||
$("#name").val("Copy of " + campaign.name)
|
||||
$("#template").val(campaign.template.name)
|
||||
$("#page").val(campaign.page.name)
|
||||
$("#profile").val(campaign.smtp.name)
|
||||
$("#url").val(campaign.url)
|
||||
$.each(campaign.groups, function(i, group){
|
||||
groupTable.row.add([
|
||||
group.name,
|
||||
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
|
||||
]).draw()
|
||||
$("#groupTable").on("click", "span>i.fa-trash-o", function() {
|
||||
groupTable.row($(this).parents('tr'))
|
||||
.remove()
|
||||
.draw();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Setup multiple modals
|
||||
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
|
||||
|
@ -183,6 +252,9 @@ $(document).ready(function() {
|
|||
}
|
||||
}, this));
|
||||
};
|
||||
$('#modal').on('hidden.bs.modal', function(event) {
|
||||
dismiss()
|
||||
});
|
||||
api.campaigns.get()
|
||||
.success(function(cs) {
|
||||
campaigns = cs
|
||||
|
@ -204,6 +276,9 @@ $(document).ready(function() {
|
|||
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
|
||||
<i class='fa fa-bar-chart'></i>\
|
||||
</a>\
|
||||
<span data-toggle='modal' data-target='#modal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Copy Campaign' onclick='copy(" + i + ")'>\
|
||||
<i class='fa fa-copy'></i>\
|
||||
</button></span>\
|
||||
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
|
||||
<i class='fa fa-trash-o'></i>\
|
||||
</button></div>"
|
||||
|
@ -329,4 +404,35 @@ $(document).ready(function() {
|
|||
.bind('typeahead:autocomplete', function(ev, page) {
|
||||
$("#page").typeahead('val', page.name)
|
||||
});
|
||||
// Create the sending profile typeahead objects
|
||||
profile_bh = new Bloodhound({
|
||||
datumTokenizer: function(s) {
|
||||
return Bloodhound.tokenizers.whitespace(s.name)
|
||||
},
|
||||
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||
local: []
|
||||
})
|
||||
profile_bh.initialize()
|
||||
$("#profile.typeahead.form-control").typeahead({
|
||||
hint: true,
|
||||
highlight: true,
|
||||
minLength: 1
|
||||
}, {
|
||||
name: "profiles",
|
||||
source: profile_bh,
|
||||
templates: {
|
||||
empty: function(data) {
|
||||
return '<div class="tt-suggestion">No profiles matched that query</div>'
|
||||
},
|
||||
suggestion: function(data) {
|
||||
return '<div>' + data.name + '</div>'
|
||||
}
|
||||
}
|
||||
})
|
||||
.bind('typeahead:select', function(ev, profile) {
|
||||
$("#profile").typeahead('val', profile.name)
|
||||
})
|
||||
.bind('typeahead:autocomplete', function(ev, profile) {
|
||||
$("#profile").typeahead('val', profile.name)
|
||||
});
|
||||
})
|
||||
|
|
|
@ -39,9 +39,10 @@ function dismiss() {
|
|||
$("#modal\\.flashes").empty()
|
||||
$("#name").val("")
|
||||
$("#html_editor").val("")
|
||||
$("#newLandingPageModal").find("input[type='checkbox']").prop("checked", false)
|
||||
$("#url").val("")
|
||||
$("#modal").find("input[type='checkbox']").prop("checked", false)
|
||||
$("#capture_passwords").hide()
|
||||
$("#newLandingPageModal").modal('hide')
|
||||
$("#modal").modal('hide')
|
||||
}
|
||||
|
||||
function deletePage(idx) {
|
||||
|
@ -127,10 +128,10 @@ function load() {
|
|||
pagesTable.row.add([
|
||||
page.name,
|
||||
moment(page.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
|
||||
"<div class='pull-right'><span data-toggle='modal' data-target='#newLandingPageModal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Edit Page' onclick='edit(" + i + ")'>\
|
||||
"<div class='pull-right'><span data-toggle='modal' data-target='#modal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Edit Page' onclick='edit(" + i + ")'>\
|
||||
<i class='fa fa-pencil'></i>\
|
||||
</button></span>\
|
||||
<span data-toggle='modal' data-target='#newLandingPageModal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Copy Page' onclick='copy(" + i + ")'>\
|
||||
<span data-toggle='modal' data-target='#modal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Copy Page' onclick='copy(" + i + ")'>\
|
||||
<i class='fa fa-copy'></i>\
|
||||
</button></span>\
|
||||
<button class='btn btn-danger' data-toggle='tooltip' data-placement='left' title='Delete Page' onclick='deletePage(" + i + ")'>\
|
||||
|
@ -187,6 +188,9 @@ $(document).ready(function() {
|
|||
}
|
||||
}, this));
|
||||
};
|
||||
$('#modal').on('hidden.bs.modal', function(event) {
|
||||
dismiss()
|
||||
});
|
||||
$("#capture_credentials_checkbox").change(function(){
|
||||
$("#capture_passwords").toggle()
|
||||
})
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
var profiles = []
|
||||
|
||||
// Attempts to send a test email by POSTing to /campaigns/
|
||||
function sendTestEmail() {
|
||||
var test_email_request = {
|
||||
template: {},
|
||||
first_name: $("input[name=to_first_name]").val(),
|
||||
last_name: $("input[name=to_last_name]").val(),
|
||||
email: $("input[name=to_email]").val(),
|
||||
position: $("input[name=to_position]").val(),
|
||||
url: '',
|
||||
smtp: {
|
||||
from_address: $("#from").val(),
|
||||
host: $("#host").val(),
|
||||
username: $("#username").val(),
|
||||
password: $("#password").val(),
|
||||
ignore_cert_errors: $("#ignore_cert_errors").prop("checked")
|
||||
}
|
||||
}
|
||||
btnHtml = $("#sendTestModalSubmit").html()
|
||||
$("#sendTestModalSubmit").html('<i class="fa fa-spinner fa-spin"></i> Sending')
|
||||
// Send the test email
|
||||
api.send_test_email(test_email_request)
|
||||
.success(function(data) {
|
||||
$("#sendTestEmailModal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-success\">\
|
||||
<i class=\"fa fa-check-circle\"></i> Email Sent!</div>")
|
||||
$("#sendTestModalSubmit").html(btnHtml)
|
||||
})
|
||||
.error(function(data) {
|
||||
$("#sendTestEmailModal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
|
||||
<i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>")
|
||||
$("#sendTestModalSubmit").html(btnHtml)
|
||||
})
|
||||
}
|
||||
|
||||
// Save attempts to POST to /smtp/
|
||||
function save(idx) {
|
||||
var profile = {}
|
||||
profile.name = $("#name").val()
|
||||
profile.interface_type = $("#interface_type").val()
|
||||
profile.from_address = $("#from").val()
|
||||
profile.host = $("#host").val()
|
||||
profile.username = $("#username").val()
|
||||
profile.password = $("#password").val()
|
||||
profile.ignore_cert_errors = $("#ignore_cert_errors").prop("checked")
|
||||
if (idx != -1) {
|
||||
profile.id = profiles[idx].id
|
||||
api.SMTPId.put(profile)
|
||||
.success(function(data) {
|
||||
successFlash("Profile edited successfully!")
|
||||
load()
|
||||
dismiss()
|
||||
})
|
||||
} else {
|
||||
// Submit the profile
|
||||
api.SMTP.post(profile)
|
||||
.success(function(data) {
|
||||
successFlash("Profile added successfully!")
|
||||
load()
|
||||
dismiss()
|
||||
})
|
||||
.error(function(data) {
|
||||
modalError(data.responseJSON.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
$("#modal\\.flashes").empty()
|
||||
$("#name").val("")
|
||||
$("#interface_type").val("SMTP")
|
||||
$("#from").val("")
|
||||
$("#host").val("")
|
||||
$("#username").val("")
|
||||
$("#password").val("")
|
||||
$("#ignore_cert_errors").prop("checked", true)
|
||||
$("#modal").modal('hide')
|
||||
}
|
||||
|
||||
function deleteProfile(idx) {
|
||||
if (confirm("Delete " + profiles[idx].name + "?")) {
|
||||
api.SMTPId.delete(profiles[idx].id)
|
||||
.success(function(data) {
|
||||
successFlash(data.message)
|
||||
load()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function edit(idx) {
|
||||
$("#modalSubmit").unbind('click').click(function() {
|
||||
save(idx)
|
||||
})
|
||||
var profile = {}
|
||||
if (idx != -1) {
|
||||
profile = profiles[idx]
|
||||
$("#name").val(profile.name)
|
||||
$("#interface_type").val(profile.interface_type)
|
||||
$("#from").val(profile.from_address)
|
||||
$("#host").val(profile.host)
|
||||
$("#username").val(profile.username)
|
||||
$("#password").val(profile.password)
|
||||
$("#ignore_cert_errors").prop("checked", profile.ignore_cert_errors)
|
||||
}
|
||||
}
|
||||
|
||||
function copy(idx) {
|
||||
$("#modalSubmit").unbind('click').click(function() {
|
||||
save(-1)
|
||||
})
|
||||
var profile = {}
|
||||
profile = profiles[idx]
|
||||
$("#name").val("Copy of " + profile.name)
|
||||
$("#interface_type").val(profile.interface_type)
|
||||
$("#from").val(profile.from_address)
|
||||
$("#host").val(profile.host)
|
||||
$("#username").val(profile.username)
|
||||
$("#password").val(profile.password)
|
||||
$("#ignore_cert_errors").prop("checked", profile.ignore_cert_errors)
|
||||
}
|
||||
|
||||
function load() {
|
||||
$("#profileTable").hide()
|
||||
$("#emptyMessage").hide()
|
||||
$("#loading").show()
|
||||
api.SMTP.get()
|
||||
.success(function(ss) {
|
||||
profiles = ss
|
||||
$("#loading").hide()
|
||||
if (profiles.length > 0) {
|
||||
$("#profileTable").show()
|
||||
profileTable = $("#profileTable").DataTable({
|
||||
destroy: true,
|
||||
columnDefs: [{
|
||||
orderable: false,
|
||||
targets: "no-sort"
|
||||
}]
|
||||
});
|
||||
profileTable.clear()
|
||||
$.each(profiles, function(i, profile) {
|
||||
profileTable.row.add([
|
||||
profile.name,
|
||||
profile.interface_type,
|
||||
moment(profile.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
|
||||
"<div class='pull-right'><span data-toggle='modal' data-target='#modal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Edit Profile' onclick='edit(" + i + ")'>\
|
||||
<i class='fa fa-pencil'></i>\
|
||||
</button></span>\
|
||||
<span data-toggle='modal' data-target='#modal'><button class='btn btn-primary' data-toggle='tooltip' data-placement='left' title='Copy Profile' onclick='copy(" + i + ")'>\
|
||||
<i class='fa fa-copy'></i>\
|
||||
</button></span>\
|
||||
<button class='btn btn-danger' data-toggle='tooltip' data-placement='left' title='Delete Profile' onclick='deleteProfile(" + i + ")'>\
|
||||
<i class='fa fa-trash-o'></i>\
|
||||
</button></div>"
|
||||
]).draw()
|
||||
})
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
} else {
|
||||
$("#emptyMessage").show()
|
||||
}
|
||||
})
|
||||
.error(function() {
|
||||
$("#loading").hide()
|
||||
errorFlash("Error fetching profiles")
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Setup multiple modals
|
||||
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
|
||||
$('.modal').on('hidden.bs.modal', function(event) {
|
||||
$(this).removeClass('fv-modal-stack');
|
||||
$('body').data('fv_open_modals', $('body').data('fv_open_modals') - 1);
|
||||
});
|
||||
$('.modal').on('shown.bs.modal', function(event) {
|
||||
// Keep track of the number of open modals
|
||||
if (typeof($('body').data('fv_open_modals')) == 'undefined') {
|
||||
$('body').data('fv_open_modals', 0);
|
||||
}
|
||||
// if the z-index of this modal has been set, ignore.
|
||||
if ($(this).hasClass('fv-modal-stack')) {
|
||||
return;
|
||||
}
|
||||
$(this).addClass('fv-modal-stack');
|
||||
// Increment the number of open modals
|
||||
$('body').data('fv_open_modals', $('body').data('fv_open_modals') + 1);
|
||||
// Setup the appropriate z-index
|
||||
$(this).css('z-index', 1040 + (10 * $('body').data('fv_open_modals')));
|
||||
$('.modal-backdrop').not('.fv-modal-stack').css('z-index', 1039 + (10 * $('body').data('fv_open_modals')));
|
||||
$('.modal-backdrop').not('fv-modal-stack').addClass('fv-modal-stack');
|
||||
});
|
||||
$.fn.modal.Constructor.prototype.enforceFocus = function() {
|
||||
$(document)
|
||||
.off('focusin.bs.modal') // guard against infinite focus loop
|
||||
.on('focusin.bs.modal', $.proxy(function(e) {
|
||||
if (
|
||||
this.$element[0] !== e.target && !this.$element.has(e.target).length
|
||||
// CKEditor compatibility fix start.
|
||||
&& !$(e.target).closest('.cke_dialog, .cke').length
|
||||
// CKEditor compatibility fix end.
|
||||
) {
|
||||
this.$element.trigger('focus');
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
$('#modal').on('hidden.bs.modal', function(event) {
|
||||
dismiss()
|
||||
});
|
||||
load()
|
||||
})
|
|
@ -64,6 +64,7 @@ function dismiss() {
|
|||
$("#modal\\.flashes").empty()
|
||||
$("#attachmentsTable").dataTable().DataTable().clear().draw()
|
||||
$("#name").val("")
|
||||
$("#subject").val("")
|
||||
$("#text_editor").val("")
|
||||
$("#html_editor").val("")
|
||||
$("#modal").modal('hide')
|
||||
|
@ -320,5 +321,8 @@ $(document).ready(function() {
|
|||
}
|
||||
}, this));
|
||||
};
|
||||
$('#modal').on('hidden.bs.modal', function(event) {
|
||||
dismiss()
|
||||
});
|
||||
load()
|
||||
})
|
||||
|
|
|
@ -58,7 +58,7 @@ var api = {
|
|||
get: function() {
|
||||
return query("/groups/", "GET", {}, false)
|
||||
},
|
||||
// post() - Posts a campaign to POST /groups
|
||||
// post() - Posts a group to POST /groups
|
||||
post: function(group) {
|
||||
return query("/groups/", "POST", group, false)
|
||||
}
|
||||
|
@ -69,11 +69,11 @@ var api = {
|
|||
get: function(id) {
|
||||
return query("/groups/" + id, "GET", {}, false)
|
||||
},
|
||||
// put() - Puts a campaign to PUT /groups/:id
|
||||
// put() - Puts a group to PUT /groups/:id
|
||||
put: function(group) {
|
||||
return query("/groups/" + group.id, "PUT", group, false)
|
||||
},
|
||||
// delete() - Deletes a campaign at DELETE /groups/:id
|
||||
// delete() - Deletes a group at DELETE /groups/:id
|
||||
delete: function(id) {
|
||||
return query("/groups/" + id, "DELETE", {}, false)
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ var api = {
|
|||
get: function() {
|
||||
return query("/templates/", "GET", {}, false)
|
||||
},
|
||||
// post() - Posts a campaign to POST /templates
|
||||
// post() - Posts a template to POST /templates
|
||||
post: function(template) {
|
||||
return query("/templates/", "POST", template, false)
|
||||
}
|
||||
|
@ -95,11 +95,11 @@ var api = {
|
|||
get: function(id) {
|
||||
return query("/templates/" + id, "GET", {}, false)
|
||||
},
|
||||
// put() - Puts a campaign to PUT /templates/:id
|
||||
// put() - Puts a template to PUT /templates/:id
|
||||
put: function(template) {
|
||||
return query("/templates/" + template.id, "PUT", template, false)
|
||||
},
|
||||
// delete() - Deletes a campaign at DELETE /templates/:id
|
||||
// delete() - Deletes a template at DELETE /templates/:id
|
||||
delete: function(id) {
|
||||
return query("/templates/" + id, "DELETE", {}, false)
|
||||
}
|
||||
|
@ -110,26 +110,52 @@ var api = {
|
|||
get: function() {
|
||||
return query("/pages/", "GET", {}, false)
|
||||
},
|
||||
// post() - Posts a campaign to POST /pages
|
||||
// post() - Posts a page to POST /pages
|
||||
post: function(page) {
|
||||
return query("/pages/", "POST", page, false)
|
||||
}
|
||||
},
|
||||
// templateId contains the endpoints for /templates/:id
|
||||
// pageId contains the endpoints for /pages/:id
|
||||
pageId: {
|
||||
// get() - Queries the API for GET /templates/:id
|
||||
// get() - Queries the API for GET /pages/:id
|
||||
get: function(id) {
|
||||
return query("/pages/" + id, "GET", {}, false)
|
||||
},
|
||||
// put() - Puts a campaign to PUT /templates/:id
|
||||
// put() - Puts a page to PUT /pages/:id
|
||||
put: function(page) {
|
||||
return query("/pages/" + page.id, "PUT", page, false)
|
||||
},
|
||||
// delete() - Deletes a campaign at DELETE /templates/:id
|
||||
// delete() - Deletes a page at DELETE /pages/:id
|
||||
delete: function(id) {
|
||||
return query("/pages/" + id, "DELETE", {}, false)
|
||||
}
|
||||
},
|
||||
// SMTP contains the endpoints for /smtp
|
||||
SMTP: {
|
||||
// get() - Queries the API for GET /smtp
|
||||
get: function() {
|
||||
return query("/smtp/", "GET", {}, false)
|
||||
},
|
||||
// post() - Posts a SMTP to POST /smtp
|
||||
post: function(smtp) {
|
||||
return query("/smtp/", "POST", smtp, false)
|
||||
}
|
||||
},
|
||||
// SMTPId contains the endpoints for /smtp/:id
|
||||
SMTPId: {
|
||||
// get() - Queries the API for GET /smtp/:id
|
||||
get: function(id) {
|
||||
return query("/smtp/" + id, "GET", {}, false)
|
||||
},
|
||||
// put() - Puts a SMTP to PUT /smtp/:id
|
||||
put: function(smtp) {
|
||||
return query("/smtp/" + smtp.id, "PUT", smtp, false)
|
||||
},
|
||||
// delete() - Deletes a SMTP at DELETE /smtp/:id
|
||||
delete: function(id) {
|
||||
return query("/smtp/" + id, "DELETE", {}, false)
|
||||
}
|
||||
},
|
||||
// import handles all of the "import" functions in the api
|
||||
import_email: function(raw) {
|
||||
return query("/import/email", "POST", {}, false)
|
||||
|
|
|
@ -63,9 +63,9 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
{{if .User}}
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -60,7 +65,7 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
@ -76,34 +81,13 @@
|
|||
<br>
|
||||
<label class="control-label" for="url">URL: <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="Location of gophish listener (must be reachable by targets!)"></i></label>
|
||||
<input type="text" class="form-control" placeholder="http://192.168.1.1" id="url"/>
|
||||
<br/>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab">
|
||||
<a role="button" class="collapsed" data-toggle="collapse" href="#smtpPanel" aria-expanded="false" aria-controls="#smtpPanel">
|
||||
SMTP Options <i class="pull-right glyphicon"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-collapse collapse" id="smtpPanel" role="tabpanel">
|
||||
<div class="panel-body">
|
||||
<label class="control-label" for="from">From:</label>
|
||||
<input type="text" class="form-control" placeholder="First Last <test@example.com>" value="" name="from">
|
||||
<br />
|
||||
<label class="control-label" for="smtp_server">Host:</label>
|
||||
<input type="text" class="form-control" placeholder="smtp.example.com:25" value="" name="host">
|
||||
<br />
|
||||
<label class="control-label" for="smtp_server">Username:</label>
|
||||
<input type="text" class="form-control" placeholder="Username" value="" name="username">
|
||||
<br />
|
||||
<label class="control-label" for="smtp_server">Password:</label>
|
||||
<input type="password" class="form-control" placeholder="Password" value="" name="password">
|
||||
<div class="checkbox checkbox-primary">
|
||||
<input id="ignore_cert_errors" type="checkbox" checked>
|
||||
<label for="ignore_cert_errors">Ignore Certificate Errors <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="Ignore common certificate errors such as self-signed certs (exposes you to MiTM attacks - use carefully!)"></i></label>
|
||||
</div>
|
||||
<button type="button" data-toggle="modal" data-target="#sendTestEmailModal" class="btn btn-primary"><i class="fa fa-envelope"></i> Send Test Email</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="control-label" for="profile">Sending Profile:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="typeahead form-control" placeholder="Sending Profile" id="profile"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="button" data-toggle="modal" data-target="#sendTestEmailModal" class="btn btn-primary typeahead-button"><i class="fa fa-envelope"></i> Send Test Email</button>
|
||||
</span>
|
||||
</div>
|
||||
<label class="control-label" for="users">Groups:</label>
|
||||
<form id="groupForm">
|
||||
<div class="input-group">
|
||||
|
@ -124,7 +108,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="dismiss()">Close</button>
|
||||
<button type="button" id="launchButton" class="btn btn-primary" onclick="launch()"><i class="fa fa-rocket"></i> Launch Campaign</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li class="active"><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -27,7 +32,7 @@
|
|||
</h1>
|
||||
<div id="flashes" class="row"></div>
|
||||
<div class="row">
|
||||
<button type="button" class="btn btn-primary" onclick="edit(-1)" data-toggle="modal" data-target="#newLandingPageModal"><i class="fa fa-plus"></i> New Page</button>
|
||||
<button type="button" class="btn btn-primary" onclick="edit(-1)" data-toggle="modal" data-target="#modal"><i class="fa fa-plus"></i> New Page</button>
|
||||
</div>
|
||||
|
||||
<div id="loading">
|
||||
|
@ -53,13 +58,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="newLandingPageModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<!-- New Template Modal -->
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="pageModalLabel">New Landing Page</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modalLabel">New Landing Page</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" id="modal.flashes"></div>
|
||||
|
@ -100,7 +105,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="importSiteModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
|
||||
<div class="modal fade" id="importSiteModal" tabindex="-1" role="dialog" aria-labelledby="importSiteModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<!-- New Template Modal -->
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
{{define "body"}}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2 sidebar">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li><a href="/">Dashboard</a>
|
||||
</li>
|
||||
<li><a href="/campaigns">Campaigns</a>
|
||||
</li>
|
||||
<li><a href="/users">Users & Groups</a>
|
||||
</li>
|
||||
<li><a href="/templates">Email Templates</a>
|
||||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li class="active"><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
|
||||
<h1 class="page-header">
|
||||
Sending Profiles
|
||||
</h1>
|
||||
<div id="flashes" class="row"></div>
|
||||
<div class="row">
|
||||
<button type="button" class="btn btn-primary" onclick="edit(-1)" data-toggle="modal" data-target="#modal"><i class="fa fa-plus"></i> New Profile</button>
|
||||
</div>
|
||||
|
||||
<div id="loading">
|
||||
<i class="fa fa-spinner fa-spin fa-4x"></i>
|
||||
</div>
|
||||
<div id="emptyMessage" class="row" style="display:none;">
|
||||
<div class="alert alert-info">
|
||||
No profiles created yet. Let's create one!
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<table id="profileTable" class="table" style="display:none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-4">Name</th>
|
||||
<th>Interface Type</th>
|
||||
<th>Last Modified Date</th>
|
||||
<th class="col-md-2 no-sort"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<!-- New Template Modal -->
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="profileModalLabel">New Sending Profile</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" id="modal.flashes"></div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="name">Name:</label>
|
||||
<input type="text" class="form-control" placeholder="Profile name" id="name" autofocus/>
|
||||
<label class="control-label" for="interface_type">Interface Type:</label>
|
||||
<input type="text" class="form-control" value="SMTP" id="interface_type" disabled/>
|
||||
<label class="control-label" for="from">From:</label>
|
||||
<input type="text" class="form-control" placeholder="First Last <test@example.com>" id="from" required/>
|
||||
<label class="control-label" for="host">Host:</label>
|
||||
<input type="text" class="form-control" placeholder="smtp.example.com:25" id="host" required/>
|
||||
<label class="control-label" for="username">Username:</label>
|
||||
<input type="text" class="form-control" placeholder="Username" id="username"/>
|
||||
<label class="control-label" for="password">Password:</label>
|
||||
<input type="text" class="form-control" placeholder="Password" id="password"/>
|
||||
<div class="checkbox checkbox-primary">
|
||||
<input id="ignore_cert_errors" type="checkbox" checked>
|
||||
<label for="ignore_cert_errors">Ignore Certificate Errors <i class="fa fa-question-circle" data-toggle="tooltip" data-placement="right" title="Ignore common certificate errors such as self-signed certs (exposes you to MiTM attacks - use carefully!)"></i></label>
|
||||
</div>
|
||||
<button type="button" data-toggle="modal" data-target="#sendTestEmailModal" class="btn btn-primary"><i class="fa fa-envelope"></i> Send Test Email</button>
|
||||
<!-- disable sendTestEmail functionality on sending profile page until update handling of /util/send_test_email -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" class="btn btn-default" onclick="dismiss()">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="modalSubmit">Save Profile</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Send Test Email Modal -->
|
||||
<div class="modal" id="sendTestEmailModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<!-- New Email Modal -->
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="sendTestEmailModalTitle">Send Test Email</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" id="sendTestEmailModal.flashes"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<label class="control-label" for="to">Send Test Email to:</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" class="form-control" placeholder="First Name" name="to_first_name">
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" class="form-control" placeholder="Last Name" name="to_last_name">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="email" class="form-control" placeholder="Email" name="to_email" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" placeholder="Position" name="to_position">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" class="btn btn-default">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="sendTestModalSubmit" onclick="sendTestEmail()"><i class="fa fa-envelope"></i> Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "scripts"}}
|
||||
<script src="/js/ckeditor/ckeditor.js"></script>
|
||||
<script src="/js/ckeditor/adapters/jquery.js"></script>
|
||||
<script src="/js/app/sending_profiles.js"></script>
|
||||
{{end}}
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li class="active"><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -60,7 +65,7 @@
|
|||
<div class="modal-content">
|
||||
<!-- New Template Modal -->
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="templateModalLabel">New Template</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
|
@ -13,8 +13,13 @@
|
|||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
</li>
|
||||
<li><hr></li>
|
||||
<li><a href="/gophish_user_guide.pdf">User Guide</a>
|
||||
</li>
|
||||
<li><a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
Loading…
Reference in New Issue