diff --git a/controllers/api.go b/controllers/api.go index 88fe51bc..50f1a560 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -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 { diff --git a/controllers/route.go b/controllers/route.go index aa17cf78..40ae2c94 100644 --- a/controllers/route.go +++ b/controllers/route.go @@ -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) diff --git a/db/migrations/20160227180335_0.1.2_store-smtp-settings.sql b/db/migrations/20160227180335_0.1.2_store-smtp-settings.sql new file mode 100644 index 00000000..1e2049a2 --- /dev/null +++ b/db/migrations/20160227180335_0.1.2_store-smtp-settings.sql @@ -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 + diff --git a/models/campaign.go b/models/campaign.go index bd39074e..827d0d02 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -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 { diff --git a/models/models_test.go b/models/models_test.go index 16811ae8..5655634c 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -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 ", + 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 ", + 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 := ` diff --git a/models/smtp.go b/models/smtp.go index cf6d69d1..65bb7e82 100644 --- a/models/smtp.go +++ b/models/smtp.go @@ -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 } diff --git a/static/gophish_user_guide.pdf b/static/gophish_user_guide.pdf new file mode 100644 index 00000000..a8eb9155 Binary files /dev/null and b/static/gophish_user_guide.pdf differ diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 870be37d..98e0ef05 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -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, + '' + ]).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() { "
\ \ \ + \
" @@ -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 '
No profiles matched that query
' + }, + suggestion: function(data) { + return '
' + data.name + '
' + } + } + }) + .bind('typeahead:select', function(ev, profile) { + $("#profile").typeahead('val', profile.name) + }) + .bind('typeahead:autocomplete', function(ev, profile) { + $("#profile").typeahead('val', profile.name) + }); }) diff --git a/static/js/app/landing_pages.js b/static/js/app/landing_pages.js index e845bfdd..a55334da 100644 --- a/static/js/app/landing_pages.js +++ b/static/js/app/landing_pages.js @@ -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'), - "
\ - \ \ + \ +
" + ]).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() +}) diff --git a/static/js/app/templates.js b/static/js/app/templates.js index 14906a11..72818255 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -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() }) diff --git a/static/js/gophish.js b/static/js/gophish.js index 8eaa6831..854d1d37 100644 --- a/static/js/gophish.js +++ b/static/js/gophish.js @@ -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) diff --git a/static/js/sending_profiles.js b/static/js/sending_profiles.js new file mode 100644 index 00000000..e69de29b diff --git a/templates/base.html b/templates/base.html index 5fdcc301..5bd9c9e1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -63,9 +63,9 @@
  • Landing Pages
  • -
  • Settings +
  • Sending Profiles
  • -
  • API Documentation +
  • Settings
  • {{if .User}} diff --git a/templates/campaign_results.html b/templates/campaign_results.html index ac4385c2..9a6c6576 100644 --- a/templates/campaign_results.html +++ b/templates/campaign_results.html @@ -13,8 +13,13 @@
  • Landing Pages
  • +
  • Sending Profiles +
  • Settings
  • +

  • +
  • User Guide +
  • API Documentation
  • diff --git a/templates/campaigns.html b/templates/campaigns.html index 234cd4db..90c2780a 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -13,8 +13,13 @@
  • Landing Pages
  • +
  • Sending Profiles +
  • Settings
  • +

  • +
  • User Guide +
  • API Documentation
  • @@ -60,7 +65,7 @@ -