mirror of https://github.com/gophish/gophish
Refactoring API into separate package for easier management. (#1411)
parent
8b27d852d8
commit
1e0a78db30
|
@ -1,772 +0,0 @@
|
||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/gophish/gophish/auth"
|
|
||||||
ctx "github.com/gophish/gophish/context"
|
|
||||||
log "github.com/gophish/gophish/logger"
|
|
||||||
"github.com/gophish/gophish/models"
|
|
||||||
"github.com/gophish/gophish/util"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/jinzhu/gorm"
|
|
||||||
"github.com/jordan-wright/email"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIReset (/api/reset) resets the currently authenticated user's API key
|
|
||||||
func (as *AdminServer) APIReset(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "POST":
|
|
||||||
u := ctx.Get(r, "user").(models.User)
|
|
||||||
u.ApiKey = auth.GenerateSecureKey()
|
|
||||||
err := models.PutUser(&u)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Error setting API Key", http.StatusInternalServerError)
|
|
||||||
} else {
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "API Key successfully reset!", Data: u.ApiKey}, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaigns returns a list of campaigns if requested via GET.
|
|
||||||
// If requested via POST, APICampaigns creates a new campaign and returns a reference to it.
|
|
||||||
func (as *AdminServer) APICampaigns(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
cs, err := models.GetCampaigns(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
JSONResponse(w, cs, http.StatusOK)
|
|
||||||
//POST: Create a new campaign and return it as JSON
|
|
||||||
case r.Method == "POST":
|
|
||||||
c := models.Campaign{}
|
|
||||||
// Put the request into a campaign
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&c)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If the campaign is scheduled to launch immediately, send it to the worker.
|
|
||||||
// Otherwise, the worker will pick it up at the scheduled time
|
|
||||||
if c.Status == models.CampaignInProgress {
|
|
||||||
go as.worker.LaunchCampaign(c)
|
|
||||||
}
|
|
||||||
JSONResponse(w, c, http.StatusCreated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaignsSummary returns the summary for the current user's campaigns
|
|
||||||
func (as *AdminServer) APICampaignsSummary(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
cs, err := models.GetCampaignSummaries(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, cs, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaign returns details about the requested campaign. If the campaign is not
|
|
||||||
// valid, APICampaign returns null.
|
|
||||||
func (as *AdminServer) APICampaign(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
JSONResponse(w, c, http.StatusOK)
|
|
||||||
case r.Method == "DELETE":
|
|
||||||
err = models.DeleteCampaign(id)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error deleting campaign"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaignResults returns just the results for a given campaign to
|
|
||||||
// significantly reduce the information returned.
|
|
||||||
func (as *AdminServer) APICampaignResults(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
cr, err := models.GetCampaignResults(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Method == "GET" {
|
|
||||||
JSONResponse(w, cr, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaignSummary returns the summary for a given campaign.
|
|
||||||
func (as *AdminServer) APICampaignSummary(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
cs, err := models.GetCampaignSummary(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, cs, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APICampaignComplete effectively "ends" a campaign.
|
|
||||||
// Future phishing emails clicked will return a simple "404" page.
|
|
||||||
func (as *AdminServer) APICampaignComplete(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
err := models.CompleteCampaign(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error completing campaign"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Campaign completed successfully!"}, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIGroups returns a list of groups if requested via GET.
|
|
||||||
// If requested via POST, APIGroups creates a new group and returns a reference to it.
|
|
||||||
func (as *AdminServer) APIGroups(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "No groups found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, gs, http.StatusOK)
|
|
||||||
//POST: Create a new group and return it as JSON
|
|
||||||
case r.Method == "POST":
|
|
||||||
g := models.Group{}
|
|
||||||
// Put the request into a group
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&g)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != gorm.ErrRecordNotFound {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.ModifiedDate = time.Now().UTC()
|
|
||||||
g.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PostGroup(&g)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, g, http.StatusCreated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIGroupsSummary returns a summary of the groups owned by the current user.
|
|
||||||
func (as *AdminServer) APIGroupsSummary(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
gs, err := models.GetGroupSummaries(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, gs, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIGroup returns details about the requested group.
|
|
||||||
// If the group is not valid, APIGroup returns null.
|
|
||||||
func (as *AdminServer) APIGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
JSONResponse(w, g, http.StatusOK)
|
|
||||||
case r.Method == "DELETE":
|
|
||||||
err = models.DeleteGroup(&g)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error deleting group"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK)
|
|
||||||
case r.Method == "PUT":
|
|
||||||
// Change this to get from URL and uid (don't bother with id in r.Body)
|
|
||||||
g = models.Group{}
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&g)
|
|
||||||
if g.Id != id {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and group_id mismatch"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
g.ModifiedDate = time.Now().UTC()
|
|
||||||
g.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PutGroup(&g)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, g, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIGroupSummary returns a summary of the groups owned by the current user.
|
|
||||||
func (as *AdminServer) APIGroupSummary(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
g, err := models.GetGroupSummary(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, g, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APITemplates handles the functionality for the /api/templates endpoint
|
|
||||||
func (as *AdminServer) APITemplates(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
ts, err := models.GetTemplates(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
JSONResponse(w, ts, http.StatusOK)
|
|
||||||
//POST: Create a new template and return it as JSON
|
|
||||||
case r.Method == "POST":
|
|
||||||
t := models.Template{}
|
|
||||||
// Put the request into a template
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&t)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != gorm.ErrRecordNotFound {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.ModifiedDate = time.Now().UTC()
|
|
||||||
t.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PostTemplate(&t)
|
|
||||||
if err == models.ErrTemplateNameNotSpecified {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == models.ErrTemplateMissingParameter {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error inserting template into database"}, http.StatusInternalServerError)
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, t, http.StatusCreated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APITemplate handles the functions for the /api/templates/:id endpoint
|
|
||||||
func (as *AdminServer) APITemplate(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Template not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
JSONResponse(w, t, http.StatusOK)
|
|
||||||
case r.Method == "DELETE":
|
|
||||||
err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error deleting template"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK)
|
|
||||||
case r.Method == "PUT":
|
|
||||||
t = models.Template{}
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&t)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
if t.Id != id {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and template_id mismatch"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.ModifiedDate = time.Now().UTC()
|
|
||||||
t.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PutTemplate(&t)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, t, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIPages handles requests for the /api/pages/ endpoint
|
|
||||||
func (as *AdminServer) APIPages(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
ps, err := models.GetPages(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
JSONResponse(w, ps, http.StatusOK)
|
|
||||||
//POST: Create a new page and return it as JSON
|
|
||||||
case r.Method == "POST":
|
|
||||||
p := models.Page{}
|
|
||||||
// Put the request into a page
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&p)
|
|
||||||
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.GetPageByName(p.Name, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != gorm.ErrRecordNotFound {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Page name already in use"}, http.StatusConflict)
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.ModifiedDate = time.Now().UTC()
|
|
||||||
p.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PostPage(&p)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, p, http.StatusCreated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIPage contains functions to handle the GET'ing, DELETE'ing, and PUT'ing
|
|
||||||
// of a Page object
|
|
||||||
func (as *AdminServer) APIPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
|
||||||
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Page not found"}, http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
JSONResponse(w, p, http.StatusOK)
|
|
||||||
case r.Method == "DELETE":
|
|
||||||
err = models.DeletePage(id, ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error deleting page"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK)
|
|
||||||
case r.Method == "PUT":
|
|
||||||
p = models.Page{}
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&p)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
if p.Id != id {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "/:id and /:page_id mismatch"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.ModifiedDate = time.Now().UTC()
|
|
||||||
p.UserId = ctx.Get(r, "user_id").(int64)
|
|
||||||
err = models.PutPage(&p)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error updating page: " + err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, p, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APISendingProfiles handles requests for the /api/smtp/ endpoint
|
|
||||||
func (as *AdminServer) APISendingProfiles(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch {
|
|
||||||
case r.Method == "GET":
|
|
||||||
ss, err := models.GetSMTPs(ctx.Get(r, "user_id").(int64))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(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.ErrRecordNotFound {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "SMTP name already in use"}, http.StatusConflict)
|
|
||||||
log.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.ModifiedDate = time.Now().UTC()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APISendingProfile contains functions to handle the GET'ing, DELETE'ing, and PUT'ing
|
|
||||||
// of a SMTP object
|
|
||||||
func (as *AdminServer) APISendingProfile(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 {
|
|
||||||
log.Error(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: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.ModifiedDate = time.Now().UTC()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIImportGroup imports a CSV of group members
|
|
||||||
func (as *AdminServer) APIImportGroup(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ts, err := util.ParseCSV(r)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, ts, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIImportEmail allows for the importing of email.
|
|
||||||
// Returns a Message object
|
|
||||||
func (as *AdminServer) APIImportEmail(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != "POST" {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ir := struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
ConvertLinks bool `json:"convert_links"`
|
|
||||||
}{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&ir)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e, err := email.NewEmailFromReader(strings.NewReader(ir.Content))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
// If the user wants to convert links to point to
|
|
||||||
// the landing page, let's make it happen by changing up
|
|
||||||
// e.HTML
|
|
||||||
if ir.ConvertLinks {
|
|
||||||
d, err := goquery.NewDocumentFromReader(bytes.NewReader(e.HTML))
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.Find("a").Each(func(i int, a *goquery.Selection) {
|
|
||||||
a.SetAttr("href", "{{.URL}}")
|
|
||||||
})
|
|
||||||
h, err := d.Html()
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e.HTML = []byte(h)
|
|
||||||
}
|
|
||||||
er := emailResponse{
|
|
||||||
Subject: e.Subject,
|
|
||||||
Text: string(e.Text),
|
|
||||||
HTML: string(e.HTML),
|
|
||||||
}
|
|
||||||
JSONResponse(w, er, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIImportSite allows for the importing of HTML from a website
|
|
||||||
// Without "include_resources" set, it will merely place a "base" tag
|
|
||||||
// so that all resources can be loaded relative to the given URL.
|
|
||||||
func (as *AdminServer) APIImportSite(w http.ResponseWriter, r *http.Request) {
|
|
||||||
cr := cloneRequest{}
|
|
||||||
if r.Method != "POST" {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&cr)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = cr.validate(); err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
resp, err := client.Get(cr.URL)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Insert the base href tag to better handle relative resources
|
|
||||||
d, err := goquery.NewDocumentFromResponse(resp)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Assuming we don't want to include resources, we'll need a base href
|
|
||||||
if d.Find("head base").Length() == 0 {
|
|
||||||
d.Find("head").PrependHtml(fmt.Sprintf("<base href=\"%s\">", cr.URL))
|
|
||||||
}
|
|
||||||
forms := d.Find("form")
|
|
||||||
forms.Each(func(i int, f *goquery.Selection) {
|
|
||||||
// We'll want to store where we got the form from
|
|
||||||
// (the current URL)
|
|
||||||
url := f.AttrOr("action", cr.URL)
|
|
||||||
if !strings.HasPrefix(url, "http") {
|
|
||||||
url = fmt.Sprintf("%s%s", cr.URL, url)
|
|
||||||
}
|
|
||||||
f.PrependHtml(fmt.Sprintf("<input type=\"hidden\" name=\"__original_url\" value=\"%s\"/>", url))
|
|
||||||
})
|
|
||||||
h, err := d.Html()
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cs := cloneResponse{HTML: h}
|
|
||||||
JSONResponse(w, cs, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// APISendTestEmail sends a test email using the template name
|
|
||||||
// and Target given.
|
|
||||||
func (as *AdminServer) APISendTestEmail(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s := &models.EmailRequest{
|
|
||||||
ErrorChan: make(chan error),
|
|
||||||
UserId: ctx.Get(r, "user_id").(int64),
|
|
||||||
}
|
|
||||||
if r.Method != "POST" {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(s)
|
|
||||||
if err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
storeRequest := false
|
|
||||||
|
|
||||||
// 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}}" +
|
|
||||||
"\nNow go send some phish!"
|
|
||||||
t := models.Template{
|
|
||||||
Subject: "Default Email from Gophish",
|
|
||||||
Text: text,
|
|
||||||
}
|
|
||||||
s.Template = t
|
|
||||||
} else {
|
|
||||||
// Get the Template requested by name
|
|
||||||
s.Template, err = models.GetTemplateByName(s.Template.Name, s.UserId)
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"template": s.Template.Name,
|
|
||||||
}).Error("Template does not exist")
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrTemplateNotFound.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.TemplateId = s.Template.Id
|
|
||||||
// We'll only save the test request to the database if there is a
|
|
||||||
// user-specified template to use.
|
|
||||||
storeRequest = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Page.Name != "" {
|
|
||||||
s.Page, err = models.GetPageByName(s.Page.Name, s.UserId)
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"page": s.Page.Name,
|
|
||||||
}).Error("Page does not exist")
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: models.ErrPageNotFound.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.PageId = s.Page.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a complete sending profile is provided use it
|
|
||||||
if err := s.SMTP.Validate(); err != nil {
|
|
||||||
// Otherwise get the SMTP requested by name
|
|
||||||
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, s.UserId)
|
|
||||||
// If the Sending Profile doesn't exist, let's err on the side
|
|
||||||
// of caution and assume that the validation failure was more important.
|
|
||||||
if lookupErr != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.SMTP = smtp
|
|
||||||
}
|
|
||||||
s.FromAddress = s.SMTP.FromAddress
|
|
||||||
|
|
||||||
// Validate the given request
|
|
||||||
if err = s.Validate(); err != nil {
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the request if this wasn't the default template
|
|
||||||
if storeRequest {
|
|
||||||
err = models.PostEmailRequest(s)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Send the test email
|
|
||||||
err = as.worker.SendTestEmail(s)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Email Sent"}, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONResponse attempts to set the status code, c, and marshal the given interface, d, into a response that
|
|
||||||
// is written to the given ResponseWriter.
|
|
||||||
func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
|
|
||||||
dj, err := json.MarshalIndent(d, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(c)
|
|
||||||
fmt.Fprintf(w, "%s", dj)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cloneRequest struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
IncludeResources bool `json:"include_resources"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cr *cloneRequest) validate() error {
|
|
||||||
if cr.URL == "" {
|
|
||||||
return errors.New("No URL Specified")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type cloneResponse struct {
|
|
||||||
HTML string `json:"html"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type emailResponse struct {
|
|
||||||
Text string `json:"text"`
|
|
||||||
HTML string `json:"html"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
}
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gophish/gophish/config"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APISuite struct {
|
||||||
|
suite.Suite
|
||||||
|
apiKey string
|
||||||
|
config *config.Config
|
||||||
|
apiServer *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) SetupSuite() {
|
||||||
|
conf := &config.Config{
|
||||||
|
DBName: "sqlite3",
|
||||||
|
DBPath: ":memory:",
|
||||||
|
MigrationsPath: "../../db/db_sqlite3/migrations/",
|
||||||
|
}
|
||||||
|
err := models.Setup(conf)
|
||||||
|
if err != nil {
|
||||||
|
s.T().Fatalf("Failed creating database: %v", err)
|
||||||
|
}
|
||||||
|
s.config = conf
|
||||||
|
s.Nil(err)
|
||||||
|
// Get the API key to use for these tests
|
||||||
|
u, err := models.GetUser(1)
|
||||||
|
s.Nil(err)
|
||||||
|
s.apiKey = u.ApiKey
|
||||||
|
// Move our cwd up to the project root for help with resolving
|
||||||
|
// static assets
|
||||||
|
err = os.Chdir("../")
|
||||||
|
s.Nil(err)
|
||||||
|
s.apiServer = NewServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) TearDownTest() {
|
||||||
|
campaigns, _ := models.GetCampaigns(1)
|
||||||
|
for _, campaign := range campaigns {
|
||||||
|
models.DeleteCampaign(campaign.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) SetupTest() {
|
||||||
|
// Add a group
|
||||||
|
group := models.Group{Name: "Test Group"}
|
||||||
|
group.Targets = []models.Target{
|
||||||
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test1@example.com", FirstName: "First", LastName: "Example"}},
|
||||||
|
models.Target{BaseRecipient: models.BaseRecipient{Email: "test2@example.com", FirstName: "Second", LastName: "Example"}},
|
||||||
|
}
|
||||||
|
group.UserId = 1
|
||||||
|
models.PostGroup(&group)
|
||||||
|
|
||||||
|
// Add a template
|
||||||
|
t := models.Template{Name: "Test Template"}
|
||||||
|
t.Subject = "Test subject"
|
||||||
|
t.Text = "Text text"
|
||||||
|
t.HTML = "<html>Test</html>"
|
||||||
|
t.UserId = 1
|
||||||
|
models.PostTemplate(&t)
|
||||||
|
|
||||||
|
// Add a landing page
|
||||||
|
p := models.Page{Name: "Test Page"}
|
||||||
|
p.HTML = "<html>Test</html>"
|
||||||
|
p.UserId = 1
|
||||||
|
models.PostPage(&p)
|
||||||
|
|
||||||
|
// Add a sending profile
|
||||||
|
smtp := models.SMTP{Name: "Test Page"}
|
||||||
|
smtp.UserId = 1
|
||||||
|
smtp.Host = "example.com"
|
||||||
|
smtp.FromAddress = "test@test.com"
|
||||||
|
models.PostSMTP(&smtp)
|
||||||
|
|
||||||
|
// Setup and "launch" our campaign
|
||||||
|
// Set the status such that no emails are attempted
|
||||||
|
c := models.Campaign{Name: "Test campaign"}
|
||||||
|
c.UserId = 1
|
||||||
|
c.Template = t
|
||||||
|
c.Page = p
|
||||||
|
c.SMTP = smtp
|
||||||
|
c.Groups = []models.Group{group}
|
||||||
|
models.PostCampaign(&c, c.UserId)
|
||||||
|
c.UpdateStatus(models.CampaignEmailsSent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APISuite) TestSiteImportBaseHref() {
|
||||||
|
h := "<html><head></head><body><img src=\"/test.png\"/></body></html>"
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintln(w, h)
|
||||||
|
}))
|
||||||
|
hr := fmt.Sprintf("<html><head><base href=\"%s\"/></head><body><img src=\"/test.png\"/>\n</body></html>", ts.URL)
|
||||||
|
defer ts.Close()
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/import/site",
|
||||||
|
bytes.NewBuffer([]byte(fmt.Sprintf(`
|
||||||
|
{
|
||||||
|
"url" : "%s",
|
||||||
|
"include_resources" : false
|
||||||
|
}
|
||||||
|
`, ts.URL))))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
s.apiServer.ImportSite(response, req)
|
||||||
|
cs := cloneResponse{}
|
||||||
|
err := json.NewDecoder(response.Body).Decode(&cs)
|
||||||
|
s.Nil(err)
|
||||||
|
s.Equal(cs.HTML, hr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPISuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(APISuite))
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Campaigns returns a list of campaigns if requested via GET.
|
||||||
|
// If requested via POST, APICampaigns creates a new campaign and returns a reference to it.
|
||||||
|
func (as *Server) Campaigns(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
cs, err := models.GetCampaigns(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
JSONResponse(w, cs, http.StatusOK)
|
||||||
|
//POST: Create a new campaign and return it as JSON
|
||||||
|
case r.Method == "POST":
|
||||||
|
c := models.Campaign{}
|
||||||
|
// Put the request into a campaign
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&c)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the campaign is scheduled to launch immediately, send it to the worker.
|
||||||
|
// Otherwise, the worker will pick it up at the scheduled time
|
||||||
|
if c.Status == models.CampaignInProgress {
|
||||||
|
go as.worker.LaunchCampaign(c)
|
||||||
|
}
|
||||||
|
JSONResponse(w, c, http.StatusCreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CampaignsSummary returns the summary for the current user's campaigns
|
||||||
|
func (as *Server) CampaignsSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
cs, err := models.GetCampaignSummaries(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, cs, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Campaign returns details about the requested campaign. If the campaign is not
|
||||||
|
// valid, APICampaign returns null.
|
||||||
|
func (as *Server) Campaign(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
JSONResponse(w, c, http.StatusOK)
|
||||||
|
case r.Method == "DELETE":
|
||||||
|
err = models.DeleteCampaign(id)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error deleting campaign"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CampaignResults returns just the results for a given campaign to
|
||||||
|
// significantly reduce the information returned.
|
||||||
|
func (as *Server) CampaignResults(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
cr, err := models.GetCampaignResults(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == "GET" {
|
||||||
|
JSONResponse(w, cr, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CampaignSummary returns the summary for a given campaign.
|
||||||
|
func (as *Server) CampaignSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
cs, err := models.GetCampaignSummary(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, cs, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CampaignComplete effectively "ends" a campaign.
|
||||||
|
// Future phishing emails clicked will return a simple "404" page.
|
||||||
|
func (as *Server) CampaignComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
err := models.CompleteCampaign(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error completing campaign"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Campaign completed successfully!"}, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Groups returns a list of groups if requested via GET.
|
||||||
|
// If requested via POST, APIGroups creates a new group and returns a reference to it.
|
||||||
|
func (as *Server) Groups(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "No groups found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, gs, http.StatusOK)
|
||||||
|
//POST: Create a new group and return it as JSON
|
||||||
|
case r.Method == "POST":
|
||||||
|
g := models.Group{}
|
||||||
|
// Put the request into a group
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&g)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.ModifiedDate = time.Now().UTC()
|
||||||
|
g.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PostGroup(&g)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, g, http.StatusCreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupsSummary returns a summary of the groups owned by the current user.
|
||||||
|
func (as *Server) GroupsSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
gs, err := models.GetGroupSummaries(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, gs, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group returns details about the requested group.
|
||||||
|
// If the group is not valid, Group returns null.
|
||||||
|
func (as *Server) Group(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
JSONResponse(w, g, http.StatusOK)
|
||||||
|
case r.Method == "DELETE":
|
||||||
|
err = models.DeleteGroup(&g)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error deleting group"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK)
|
||||||
|
case r.Method == "PUT":
|
||||||
|
// Change this to get from URL and uid (don't bother with id in r.Body)
|
||||||
|
g = models.Group{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&g)
|
||||||
|
if g.Id != id {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and group_id mismatch"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.ModifiedDate = time.Now().UTC()
|
||||||
|
g.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PutGroup(&g)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, g, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupSummary returns a summary of the groups owned by the current user.
|
||||||
|
func (as *Server) GroupSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
g, err := models.GetGroupSummary(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, g, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gophish/gophish/util"
|
||||||
|
"github.com/jordan-wright/email"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cloneRequest struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
IncludeResources bool `json:"include_resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *cloneRequest) validate() error {
|
||||||
|
if cr.URL == "" {
|
||||||
|
return errors.New("No URL Specified")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloneResponse struct {
|
||||||
|
HTML string `json:"html"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type emailResponse struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
HTML string `json:"html"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportGroup imports a CSV of group members
|
||||||
|
func (as *Server) ImportGroup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ts, err := util.ParseCSV(r)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, ts, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportEmail allows for the importing of email.
|
||||||
|
// Returns a Message object
|
||||||
|
func (as *Server) ImportEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ir := struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
ConvertLinks bool `json:"convert_links"`
|
||||||
|
}{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&ir)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e, err := email.NewEmailFromReader(strings.NewReader(ir.Content))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
// If the user wants to convert links to point to
|
||||||
|
// the landing page, let's make it happen by changing up
|
||||||
|
// e.HTML
|
||||||
|
if ir.ConvertLinks {
|
||||||
|
d, err := goquery.NewDocumentFromReader(bytes.NewReader(e.HTML))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.Find("a").Each(func(i int, a *goquery.Selection) {
|
||||||
|
a.SetAttr("href", "{{.URL}}")
|
||||||
|
})
|
||||||
|
h, err := d.Html()
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.HTML = []byte(h)
|
||||||
|
}
|
||||||
|
er := emailResponse{
|
||||||
|
Subject: e.Subject,
|
||||||
|
Text: string(e.Text),
|
||||||
|
HTML: string(e.HTML),
|
||||||
|
}
|
||||||
|
JSONResponse(w, er, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportSite allows for the importing of HTML from a website
|
||||||
|
// Without "include_resources" set, it will merely place a "base" tag
|
||||||
|
// so that all resources can be loaded relative to the given URL.
|
||||||
|
func (as *Server) ImportSite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cr := cloneRequest{}
|
||||||
|
if r.Method != "POST" {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&cr)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = cr.validate(); err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
resp, err := client.Get(cr.URL)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Insert the base href tag to better handle relative resources
|
||||||
|
d, err := goquery.NewDocumentFromResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Assuming we don't want to include resources, we'll need a base href
|
||||||
|
if d.Find("head base").Length() == 0 {
|
||||||
|
d.Find("head").PrependHtml(fmt.Sprintf("<base href=\"%s\">", cr.URL))
|
||||||
|
}
|
||||||
|
forms := d.Find("form")
|
||||||
|
forms.Each(func(i int, f *goquery.Selection) {
|
||||||
|
// We'll want to store where we got the form from
|
||||||
|
// (the current URL)
|
||||||
|
url := f.AttrOr("action", cr.URL)
|
||||||
|
if !strings.HasPrefix(url, "http") {
|
||||||
|
url = fmt.Sprintf("%s%s", cr.URL, url)
|
||||||
|
}
|
||||||
|
f.PrependHtml(fmt.Sprintf("<input type=\"hidden\" name=\"__original_url\" value=\"%s\"/>", url))
|
||||||
|
})
|
||||||
|
h, err := d.Html()
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs := cloneResponse{HTML: h}
|
||||||
|
JSONResponse(w, cs, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pages handles requests for the /api/pages/ endpoint
|
||||||
|
func (as *Server) Pages(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
ps, err := models.GetPages(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
JSONResponse(w, ps, http.StatusOK)
|
||||||
|
//POST: Create a new page and return it as JSON
|
||||||
|
case r.Method == "POST":
|
||||||
|
p := models.Page{}
|
||||||
|
// Put the request into a page
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&p)
|
||||||
|
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.GetPageByName(p.Name, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Page name already in use"}, http.StatusConflict)
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.ModifiedDate = time.Now().UTC()
|
||||||
|
p.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PostPage(&p)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, p, http.StatusCreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page contains functions to handle the GET'ing, DELETE'ing, and PUT'ing
|
||||||
|
// of a Page object
|
||||||
|
func (as *Server) Page(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Page not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
JSONResponse(w, p, http.StatusOK)
|
||||||
|
case r.Method == "DELETE":
|
||||||
|
err = models.DeletePage(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error deleting page"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK)
|
||||||
|
case r.Method == "PUT":
|
||||||
|
p = models.Page{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&p)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
if p.Id != id {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "/:id and /:page_id mismatch"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.ModifiedDate = time.Now().UTC()
|
||||||
|
p.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PutPage(&p)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error updating page: " + err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, p, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gophish/gophish/auth"
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset (/api/reset) resets the currently authenticated user's API key
|
||||||
|
func (as *Server) Reset(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "POST":
|
||||||
|
u := ctx.Get(r, "user").(models.User)
|
||||||
|
u.ApiKey = auth.GenerateSecureKey()
|
||||||
|
err := models.PutUser(&u)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error setting API Key", http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "API Key successfully reset!", Data: u.ApiKey}, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONResponse attempts to set the status code, c, and marshal the given interface, d, into a response that
|
||||||
|
// is written to the given ResponseWriter.
|
||||||
|
func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
|
||||||
|
dj, err := json.MarshalIndent(d, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(c)
|
||||||
|
fmt.Fprintf(w, "%s", dj)
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
mid "github.com/gophish/gophish/middleware"
|
||||||
|
"github.com/gophish/gophish/worker"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerOption is an option to apply to the API server.
|
||||||
|
type ServerOption func(*Server)
|
||||||
|
|
||||||
|
// Server represents the routes and functionality of the Gophish API.
|
||||||
|
// It's not a server in the traditional sense, in that it isn't started and
|
||||||
|
// stopped. Rather, it's meant to be used as an http.Handler in the
|
||||||
|
// AdminServer.
|
||||||
|
type Server struct {
|
||||||
|
handler http.Handler
|
||||||
|
worker worker.Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns a new instance of the API handler with the provided
|
||||||
|
// options applied.
|
||||||
|
func NewServer(options ...ServerOption) *Server {
|
||||||
|
defaultWorker, _ := worker.New()
|
||||||
|
as := &Server{
|
||||||
|
worker: defaultWorker,
|
||||||
|
}
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(as)
|
||||||
|
}
|
||||||
|
as.registerRoutes()
|
||||||
|
return as
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWorker is an option that sets the background worker.
|
||||||
|
func WithWorker(w worker.Worker) ServerOption {
|
||||||
|
return func(as *Server) {
|
||||||
|
as.worker = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *Server) registerRoutes() {
|
||||||
|
root := mux.NewRouter()
|
||||||
|
root = root.StrictSlash(true)
|
||||||
|
router := root.PathPrefix("/api/").Subrouter()
|
||||||
|
router.Use(mid.RequireAPIKey)
|
||||||
|
router.Use(mid.EnforceViewOnly)
|
||||||
|
router.HandleFunc("/reset", as.Reset)
|
||||||
|
router.HandleFunc("/campaigns/", as.Campaigns)
|
||||||
|
router.HandleFunc("/campaigns/summary", as.CampaignsSummary)
|
||||||
|
router.HandleFunc("/campaigns/{id:[0-9]+}", as.Campaign)
|
||||||
|
router.HandleFunc("/campaigns/{id:[0-9]+}/results", as.CampaignResults)
|
||||||
|
router.HandleFunc("/campaigns/{id:[0-9]+}/summary", as.CampaignSummary)
|
||||||
|
router.HandleFunc("/campaigns/{id:[0-9]+}/complete", as.CampaignComplete)
|
||||||
|
router.HandleFunc("/groups/", as.Groups)
|
||||||
|
router.HandleFunc("/groups/summary", as.GroupsSummary)
|
||||||
|
router.HandleFunc("/groups/{id:[0-9]+}", as.Group)
|
||||||
|
router.HandleFunc("/groups/{id:[0-9]+}/summary", as.GroupSummary)
|
||||||
|
router.HandleFunc("/templates/", as.Templates)
|
||||||
|
router.HandleFunc("/templates/{id:[0-9]+}", as.Template)
|
||||||
|
router.HandleFunc("/pages/", as.Pages)
|
||||||
|
router.HandleFunc("/pages/{id:[0-9]+}", as.Page)
|
||||||
|
router.HandleFunc("/smtp/", as.SendingProfiles)
|
||||||
|
router.HandleFunc("/smtp/{id:[0-9]+}", as.SendingProfile)
|
||||||
|
router.HandleFunc("/util/send_test_email", as.SendTestEmail)
|
||||||
|
router.HandleFunc("/import/group", as.ImportGroup)
|
||||||
|
router.HandleFunc("/import/email", as.ImportEmail)
|
||||||
|
router.HandleFunc("/import/site", as.ImportSite)
|
||||||
|
as.handler = router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
as.handler.ServeHTTP(w, r)
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendingProfiles handles requests for the /api/smtp/ endpoint
|
||||||
|
func (as *Server) SendingProfiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
ss, err := models.GetSMTPs(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(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.ErrRecordNotFound {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "SMTP name already in use"}, http.StatusConflict)
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.ModifiedDate = time.Now().UTC()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendingProfile contains functions to handle the GET'ing, DELETE'ing, and PUT'ing
|
||||||
|
// of a SMTP object
|
||||||
|
func (as *Server) SendingProfile(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 {
|
||||||
|
log.Error(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: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.ModifiedDate = time.Now().UTC()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Templates handles the functionality for the /api/templates endpoint
|
||||||
|
func (as *Server) Templates(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
ts, err := models.GetTemplates(ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
JSONResponse(w, ts, http.StatusOK)
|
||||||
|
//POST: Create a new template and return it as JSON
|
||||||
|
case r.Method == "POST":
|
||||||
|
t := models.Template{}
|
||||||
|
// Put the request into a template
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&t)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ModifiedDate = time.Now().UTC()
|
||||||
|
t.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PostTemplate(&t)
|
||||||
|
if err == models.ErrTemplateNameNotSpecified {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == models.ErrTemplateMissingParameter {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error inserting template into database"}, http.StatusInternalServerError)
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, t, http.StatusCreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template handles the functions for the /api/templates/:id endpoint
|
||||||
|
func (as *Server) Template(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
|
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Template not found"}, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case r.Method == "GET":
|
||||||
|
JSONResponse(w, t, http.StatusOK)
|
||||||
|
case r.Method == "DELETE":
|
||||||
|
err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64))
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error deleting template"}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK)
|
||||||
|
case r.Method == "PUT":
|
||||||
|
t = models.Template{}
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
if t.Id != id {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and template_id mismatch"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.ModifiedDate = time.Now().UTC()
|
||||||
|
t.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
|
err = models.PutTemplate(&t)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, t, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
log "github.com/gophish/gophish/logger"
|
||||||
|
"github.com/gophish/gophish/models"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendTestEmail sends a test email using the template name
|
||||||
|
// and Target given.
|
||||||
|
func (as *Server) SendTestEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s := &models.EmailRequest{
|
||||||
|
ErrorChan: make(chan error),
|
||||||
|
UserId: ctx.Get(r, "user_id").(int64),
|
||||||
|
}
|
||||||
|
if r.Method != "POST" {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Method not allowed"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(s)
|
||||||
|
if err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: "Error decoding JSON Request"}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storeRequest := false
|
||||||
|
|
||||||
|
// 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}}" +
|
||||||
|
"\nNow go send some phish!"
|
||||||
|
t := models.Template{
|
||||||
|
Subject: "Default Email from Gophish",
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
s.Template = t
|
||||||
|
} else {
|
||||||
|
// Get the Template requested by name
|
||||||
|
s.Template, err = models.GetTemplateByName(s.Template.Name, s.UserId)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"template": s.Template.Name,
|
||||||
|
}).Error("Template does not exist")
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: models.ErrTemplateNotFound.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.TemplateId = s.Template.Id
|
||||||
|
// We'll only save the test request to the database if there is a
|
||||||
|
// user-specified template to use.
|
||||||
|
storeRequest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Page.Name != "" {
|
||||||
|
s.Page, err = models.GetPageByName(s.Page.Name, s.UserId)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"page": s.Page.Name,
|
||||||
|
}).Error("Page does not exist")
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: models.ErrPageNotFound.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.PageId = s.Page.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a complete sending profile is provided use it
|
||||||
|
if err := s.SMTP.Validate(); err != nil {
|
||||||
|
// Otherwise get the SMTP requested by name
|
||||||
|
smtp, lookupErr := models.GetSMTPByName(s.SMTP.Name, s.UserId)
|
||||||
|
// If the Sending Profile doesn't exist, let's err on the side
|
||||||
|
// of caution and assume that the validation failure was more important.
|
||||||
|
if lookupErr != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.SMTP = smtp
|
||||||
|
}
|
||||||
|
s.FromAddress = s.SMTP.FromAddress
|
||||||
|
|
||||||
|
// Validate the given request
|
||||||
|
if err = s.Validate(); err != nil {
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the request if this wasn't the default template
|
||||||
|
if storeRequest {
|
||||||
|
err = models.PostEmailRequest(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send the test email
|
||||||
|
err = as.worker.SendTestEmail(s)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
JSONResponse(w, models.Response{Success: true, Message: "Email Sent"}, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,10 +1,6 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -103,52 +99,6 @@ func (s *ControllersSuite) SetupTest() {
|
||||||
c.UpdateStatus(models.CampaignEmailsSent)
|
c.UpdateStatus(models.CampaignEmailsSent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) TestRequireAPIKey() {
|
|
||||||
resp, err := http.Post(fmt.Sprintf("%s/api/import/site", s.adminServer.URL), "application/json", nil)
|
|
||||||
s.Nil(err)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
s.Equal(resp.StatusCode, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ControllersSuite) TestInvalidAPIKey() {
|
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/api/groups/?api_key=%s", s.adminServer.URL, "bogus-api-key"))
|
|
||||||
s.Nil(err)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
s.Equal(resp.StatusCode, http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ControllersSuite) TestBearerToken() {
|
|
||||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/groups/", s.adminServer.URL), nil)
|
|
||||||
s.Nil(err)
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", s.apiKey))
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
s.Nil(err)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
s.Equal(resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ControllersSuite) TestSiteImportBaseHref() {
|
|
||||||
h := "<html><head></head><body><img src=\"/test.png\"/></body></html>"
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintln(w, h)
|
|
||||||
}))
|
|
||||||
hr := fmt.Sprintf("<html><head><base href=\"%s\"/></head><body><img src=\"/test.png\"/>\n</body></html>", ts.URL)
|
|
||||||
defer ts.Close()
|
|
||||||
resp, err := http.Post(fmt.Sprintf("%s/api/import/site?api_key=%s", s.adminServer.URL, s.apiKey), "application/json",
|
|
||||||
bytes.NewBuffer([]byte(fmt.Sprintf(`
|
|
||||||
{
|
|
||||||
"url" : "%s",
|
|
||||||
"include_resources" : false
|
|
||||||
}
|
|
||||||
`, ts.URL))))
|
|
||||||
s.Nil(err)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
cs := cloneResponse{}
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&cs)
|
|
||||||
s.Nil(err)
|
|
||||||
s.Equal(cs.HTML, hr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ControllersSuite) TearDownSuite() {
|
func (s *ControllersSuite) TearDownSuite() {
|
||||||
// Tear down the admin and phishing servers
|
// Tear down the admin and phishing servers
|
||||||
s.adminServer.Close()
|
s.adminServer.Close()
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/gophish/gophish/config"
|
"github.com/gophish/gophish/config"
|
||||||
ctx "github.com/gophish/gophish/context"
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
"github.com/gophish/gophish/controllers/api"
|
||||||
log "github.com/gophish/gophish/logger"
|
log "github.com/gophish/gophish/logger"
|
||||||
"github.com/gophish/gophish/models"
|
"github.com/gophish/gophish/models"
|
||||||
"github.com/gophish/gophish/util"
|
"github.com/gophish/gophish/util"
|
||||||
|
@ -299,7 +300,7 @@ func (ps *PhishingServer) TransparencyHandler(w http.ResponseWriter, r *http.Req
|
||||||
SendDate: rs.SendDate,
|
SendDate: rs.SendDate,
|
||||||
ContactAddress: ps.contactAddress,
|
ContactAddress: ps.contactAddress,
|
||||||
}
|
}
|
||||||
JSONResponse(w, tr, http.StatusOK)
|
api.JSONResponse(w, tr, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupContext handles some of the administrative work around receiving a new
|
// setupContext handles some of the administrative work around receiving a new
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/gophish/gophish/auth"
|
"github.com/gophish/gophish/auth"
|
||||||
"github.com/gophish/gophish/config"
|
"github.com/gophish/gophish/config"
|
||||||
ctx "github.com/gophish/gophish/context"
|
ctx "github.com/gophish/gophish/context"
|
||||||
|
"github.com/gophish/gophish/controllers/api"
|
||||||
log "github.com/gophish/gophish/logger"
|
log "github.com/gophish/gophish/logger"
|
||||||
mid "github.com/gophish/gophish/middleware"
|
mid "github.com/gophish/gophish/middleware"
|
||||||
"github.com/gophish/gophish/models"
|
"github.com/gophish/gophish/models"
|
||||||
|
@ -106,31 +107,8 @@ func (as *AdminServer) registerRoutes() {
|
||||||
router.HandleFunc("/settings", Use(as.Settings, mid.RequireLogin))
|
router.HandleFunc("/settings", Use(as.Settings, mid.RequireLogin))
|
||||||
router.HandleFunc("/register", Use(as.Register, mid.RequireLogin, mid.RequirePermission(models.PermissionModifySystem)))
|
router.HandleFunc("/register", Use(as.Register, mid.RequireLogin, mid.RequirePermission(models.PermissionModifySystem)))
|
||||||
// Create the API routes
|
// Create the API routes
|
||||||
api := router.PathPrefix("/api").Subrouter()
|
api := api.NewServer(api.WithWorker(as.worker))
|
||||||
api = api.StrictSlash(true)
|
router.PathPrefix("/api/").Handler(api)
|
||||||
api.Use(mid.RequireAPIKey)
|
|
||||||
api.Use(mid.EnforceViewOnly)
|
|
||||||
api.HandleFunc("/reset", as.APIReset)
|
|
||||||
api.HandleFunc("/campaigns/", as.APICampaigns)
|
|
||||||
api.HandleFunc("/campaigns/summary", as.APICampaignsSummary)
|
|
||||||
api.HandleFunc("/campaigns/{id:[0-9]+}", as.APICampaign)
|
|
||||||
api.HandleFunc("/campaigns/{id:[0-9]+}/results", as.APICampaignResults)
|
|
||||||
api.HandleFunc("/campaigns/{id:[0-9]+}/summary", as.APICampaignSummary)
|
|
||||||
api.HandleFunc("/campaigns/{id:[0-9]+}/complete", as.APICampaignComplete)
|
|
||||||
api.HandleFunc("/groups/", as.APIGroups)
|
|
||||||
api.HandleFunc("/groups/summary", as.APIGroupsSummary)
|
|
||||||
api.HandleFunc("/groups/{id:[0-9]+}", as.APIGroup)
|
|
||||||
api.HandleFunc("/groups/{id:[0-9]+}/summary", as.APIGroupSummary)
|
|
||||||
api.HandleFunc("/templates/", as.APITemplates)
|
|
||||||
api.HandleFunc("/templates/{id:[0-9]+}", as.APITemplate)
|
|
||||||
api.HandleFunc("/pages/", as.APIPages)
|
|
||||||
api.HandleFunc("/pages/{id:[0-9]+}", as.APIPage)
|
|
||||||
api.HandleFunc("/smtp/", as.APISendingProfiles)
|
|
||||||
api.HandleFunc("/smtp/{id:[0-9]+}", as.APISendingProfile)
|
|
||||||
api.HandleFunc("/util/send_test_email", as.APISendTestEmail)
|
|
||||||
api.HandleFunc("/import/group", as.APIImportGroup)
|
|
||||||
api.HandleFunc("/import/email", as.APIImportEmail)
|
|
||||||
api.HandleFunc("/import/site", as.APIImportSite)
|
|
||||||
|
|
||||||
// Setup static file serving
|
// Setup static file serving
|
||||||
router.PathPrefix("/").Handler(http.FileServer(unindexed.Dir("./static/")))
|
router.PathPrefix("/").Handler(http.FileServer(unindexed.Dir("./static/")))
|
||||||
|
@ -280,16 +258,16 @@ func (as *AdminServer) Settings(w http.ResponseWriter, r *http.Request) {
|
||||||
if err == auth.ErrInvalidPassword {
|
if err == auth.ErrInvalidPassword {
|
||||||
msg.Message = "Invalid Password"
|
msg.Message = "Invalid Password"
|
||||||
msg.Success = false
|
msg.Success = false
|
||||||
JSONResponse(w, msg, http.StatusBadRequest)
|
api.JSONResponse(w, msg, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg.Message = err.Error()
|
msg.Message = err.Error()
|
||||||
msg.Success = false
|
msg.Success = false
|
||||||
JSONResponse(w, msg, http.StatusBadRequest)
|
api.JSONResponse(w, msg, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, msg, http.StatusOK)
|
api.JSONResponse(w, msg, http.StatusOK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -17,6 +18,7 @@ var successHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Reques
|
||||||
|
|
||||||
type MiddlewareSuite struct {
|
type MiddlewareSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
apiKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MiddlewareSuite) SetupSuite() {
|
func (s *MiddlewareSuite) SetupSuite() {
|
||||||
|
@ -29,6 +31,10 @@ func (s *MiddlewareSuite) SetupSuite() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.T().Fatalf("Failed creating database: %v", err)
|
s.T().Fatalf("Failed creating database: %v", err)
|
||||||
}
|
}
|
||||||
|
// Get the API key to use for these tests
|
||||||
|
u, err := models.GetUser(1)
|
||||||
|
s.Nil(err)
|
||||||
|
s.apiKey = u.ApiKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// MiddlewarePermissionTest maps an expected HTTP Method to an expected HTTP
|
// MiddlewarePermissionTest maps an expected HTTP Method to an expected HTTP
|
||||||
|
@ -99,6 +105,35 @@ func (s *MiddlewareSuite) TestRequirePermission() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestRequireAPIKey() {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
// Test that making a request without an API key is denied
|
||||||
|
RequireAPIKey(successHandler).ServeHTTP(response, req)
|
||||||
|
s.Equal(response.Code, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestInvalidAPIKey() {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
query := req.URL.Query()
|
||||||
|
query.Set("api_key", "bogus-api-key")
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
RequireAPIKey(successHandler).ServeHTTP(response, req)
|
||||||
|
s.Equal(response.Code, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MiddlewareSuite) TestBearerToken() {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", s.apiKey))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
RequireAPIKey(successHandler).ServeHTTP(response, req)
|
||||||
|
s.Equal(response.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMiddlewareSuite(t *testing.T) {
|
func TestMiddlewareSuite(t *testing.T) {
|
||||||
suite.Run(t, new(MiddlewareSuite))
|
suite.Run(t, new(MiddlewareSuite))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue