Merge pull request #169 from gophish/78-store-smtp-settings

Added storage of SMTP settings to database. Closes #78
pull/186/head
Jordan Wright 2016-02-28 22:16:30 -06:00
commit af91483cd4
22 changed files with 894 additions and 97 deletions

View File

@ -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,7 +520,25 @@ 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
// 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)
@ -448,6 +548,22 @@ func API_Send_Test_Email(w http.ResponseWriter, r *http.Request) {
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 {

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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>

View File

@ -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"`
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" sql:"-"`
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.

View File

@ -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,7 +141,77 @@ 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() {
@ -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)
});
})

View File

@ -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()
})

View File

@ -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()
})

View File

@ -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()
})

View File

@ -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)

View File

View File

@ -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}}

View File

@ -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>

View File

@ -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">&times;</span></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
</div>
<div class="modal-body">
@ -76,33 +81,12 @@
<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>
<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">
@ -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>

View File

@ -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>

View File

@ -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>
&nbsp;
<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">&times;</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">&times;</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 -->

View File

@ -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 &amp; 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>
&nbsp;
<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">&times;</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">&times;</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}}

View File

@ -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>

View File

@ -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">&times;</span></button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="dismiss()"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="templateModalLabel">New Template</h4>
</div>
<div class="modal-body">

View File

@ -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>