mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-09-24 18:00:50 +02:00
Initial open source release
This commit is contained in:
@@ -0,0 +1,470 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/oapi-codegen/nullable"
|
||||
"github.com/phishingclub/phishingclub/cache"
|
||||
"github.com/phishingclub/phishingclub/database"
|
||||
"github.com/phishingclub/phishingclub/model"
|
||||
"github.com/phishingclub/phishingclub/repository"
|
||||
"github.com/phishingclub/phishingclub/service"
|
||||
"github.com/phishingclub/phishingclub/utils"
|
||||
)
|
||||
|
||||
// recipientColumnByMap is a map between the frontend and the backend
|
||||
// so the frontend has user friendly names instead of direct references
|
||||
// to the database schema
|
||||
// this is tied to a slice in the repository package
|
||||
var recipientColumnByMap = map[string]string{
|
||||
"created_at": repository.TableColumn(database.RECIPIENT_TABLE, "created_at"),
|
||||
"updated_at": repository.TableColumn(database.RECIPIENT_TABLE, "updated_at"),
|
||||
"email": repository.TableColumn(database.RECIPIENT_TABLE, "email"),
|
||||
"phone": repository.TableColumn(database.RECIPIENT_TABLE, "phone"),
|
||||
"extra identifier": repository.TableColumn(database.RECIPIENT_TABLE, "extra_identifier"),
|
||||
"first_name": repository.TableColumn(database.RECIPIENT_TABLE, "first_name"),
|
||||
"last_name": repository.TableColumn(database.RECIPIENT_TABLE, "last_name"),
|
||||
"position": repository.TableColumn(database.RECIPIENT_TABLE, "position"),
|
||||
"department": repository.TableColumn(database.RECIPIENT_TABLE, "department"),
|
||||
"city": repository.TableColumn(database.RECIPIENT_TABLE, "city"),
|
||||
"country": repository.TableColumn(database.RECIPIENT_TABLE, "country"),
|
||||
"misc": repository.TableColumn(database.RECIPIENT_TABLE, "misc"),
|
||||
"repeat_offender": "is_repeat_offender", // Special case - don't use TableColumn
|
||||
}
|
||||
|
||||
var recipientCampaignEventColumnMap = utils.MergeStringMaps(
|
||||
campaignEventColumns,
|
||||
map[string]string{
|
||||
"event": repository.TableColumnName(database.EVENT_TABLE),
|
||||
"created": repository.TableColumn(database.CAMPAIGN_EVENT_TABLE, "created_at"),
|
||||
"campaign": repository.TableColumn(database.CAMPAIGN_TABLE, "name"),
|
||||
},
|
||||
)
|
||||
|
||||
// Recipient is a Recipient controller
|
||||
type Recipient struct {
|
||||
Common
|
||||
RecipientService *service.Recipient
|
||||
}
|
||||
|
||||
// Create inserts a new recipient
|
||||
func (r *Recipient) Create(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
var req model.Recipient
|
||||
if ok := r.handleParseRequest(g, &req); !ok {
|
||||
return
|
||||
}
|
||||
// save recipient
|
||||
id, err := r.RecipientService.Create(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
&req,
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(
|
||||
g,
|
||||
gin.H{
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// GetCampaignEvents gets all campaign events by recipient id and campaign id
|
||||
// gets all events if campaign id is nil
|
||||
func (r *Recipient) GetCampaignEvents(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
recipientID, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// optional param
|
||||
var campaignID *uuid.UUID
|
||||
cid, err := uuid.Parse(g.Query("campaignID"))
|
||||
if err == nil {
|
||||
campaignID = &cid
|
||||
}
|
||||
queryArgs, ok := r.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
queryArgs.DefaultSortByCreatedAt()
|
||||
// remap query args
|
||||
queryArgs.RemapOrderBy(recipientCampaignEventColumnMap)
|
||||
// get events
|
||||
events, err := r.RecipientService.GetAllCampaignEvents(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
recipientID,
|
||||
campaignID,
|
||||
queryArgs,
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, events)
|
||||
}
|
||||
|
||||
// Export outputs a zip with recipient, groups and all events related to the recipient
|
||||
func (r *Recipient) Export(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
recipientID, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// get the recipient
|
||||
recp, err := r.RecipientService.GetByID(
|
||||
g,
|
||||
session,
|
||||
recipientID,
|
||||
&repository.RecipientOption{
|
||||
WithCompany: true,
|
||||
WithGroups: true,
|
||||
},
|
||||
)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
recipientBuffer := &bytes.Buffer{}
|
||||
recipientWriter := csv.NewWriter(recipientBuffer)
|
||||
recpHeaders := []string{
|
||||
"Created at",
|
||||
"Updated at",
|
||||
"Email",
|
||||
"Phone",
|
||||
"Extra Identifier",
|
||||
"Name",
|
||||
"Position",
|
||||
"Department",
|
||||
"City",
|
||||
"Country",
|
||||
"Misc",
|
||||
}
|
||||
groups, _ := recp.Groups.Get()
|
||||
for i := range groups {
|
||||
recpHeaders = append(recpHeaders, fmt.Sprintf("Group %d", i+1))
|
||||
}
|
||||
err = recipientWriter.Write(recpHeaders)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
row := []string{
|
||||
utils.CSVFromDate(recp.CreatedAt),
|
||||
utils.CSVFromDate(recp.UpdatedAt),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Email)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Phone)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.ExtraIdentifier)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.FirstName)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.LastName)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Position)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Department)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.City)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Country)),
|
||||
utils.CSVRemoveFormulaStart(utils.NullableToString(recp.Misc)),
|
||||
}
|
||||
for _, group := range groups {
|
||||
row = append(row, group.Name.MustGet().String())
|
||||
}
|
||||
err = recipientWriter.Write(row)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
recipientWriter.Flush()
|
||||
|
||||
queryArgs, ok := r.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
queryArgs.DefaultSortByCreatedAt()
|
||||
// remap query args
|
||||
queryArgs.RemapOrderBy(recipientCampaignEventColumnMap)
|
||||
sortOrder := g.DefaultQuery("sortOrder", "desc")
|
||||
if sortOrder == "desc" {
|
||||
queryArgs.Desc = true
|
||||
}
|
||||
|
||||
// get all rows
|
||||
queryArgs.Limit = 0
|
||||
queryArgs.Offset = 0
|
||||
// get events
|
||||
events, err := r.RecipientService.GetAllCampaignEvents(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
recipientID,
|
||||
nil,
|
||||
queryArgs,
|
||||
)
|
||||
// handle response
|
||||
eventsBuffer := &bytes.Buffer{}
|
||||
eventsWriter := csv.NewWriter(eventsBuffer)
|
||||
|
||||
headers := []string{
|
||||
"Created at",
|
||||
"Campaign",
|
||||
"IP",
|
||||
"User-Agent",
|
||||
"Event Details",
|
||||
"Event",
|
||||
}
|
||||
err = eventsWriter.Write(headers)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
for _, event := range events.Rows {
|
||||
row := []string{}
|
||||
row = []string{
|
||||
utils.CSVFromDate(event.CreatedAt),
|
||||
utils.CSVRemoveFormulaStart(event.CampaignName),
|
||||
utils.CSVRemoveFormulaStart(event.IP.String()),
|
||||
utils.CSVRemoveFormulaStart(event.UserAgent.String()),
|
||||
utils.CSVRemoveFormulaStart(event.Data.String()),
|
||||
utils.CSVRemoveFormulaStart(cache.EventNameByID[event.EventID.String()]),
|
||||
}
|
||||
err = eventsWriter.Write(row)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
eventsWriter.Flush()
|
||||
|
||||
// create ZIP file in memory
|
||||
zipBuffer := new(bytes.Buffer)
|
||||
zipWriter := zip.NewWriter(zipBuffer)
|
||||
zipFileName := fmt.Sprintf("recipient_export_%s.zip", recp.Email.MustGet().String())
|
||||
|
||||
// add events to zip
|
||||
{
|
||||
f, err := zipWriter.Create("recipient.csv")
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
_, err = f.Write(recipientBuffer.Bytes())
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
// add events to zip
|
||||
{
|
||||
f, err := zipWriter.Create("events.csv")
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
_, err = f.Write(eventsBuffer.Bytes())
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
// close zip
|
||||
err = zipWriter.Close()
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.responseWithZIP(g, zipBuffer, zipFileName)
|
||||
}
|
||||
|
||||
// GetRepeatOffenderCount gets the repeat offender count
|
||||
func (r *Recipient) GetRepeatOffenderCount(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
|
||||
// get count
|
||||
count, err := r.RecipientService.GetRepeatOffenderCount(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
companyID,
|
||||
)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.OK(g, count)
|
||||
}
|
||||
|
||||
// GetAll gets all recipients
|
||||
func (r *Recipient) GetAll(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
companyID := companyIDFromRequestQuery(g)
|
||||
queryArgs, ok := r.handleQueryArgs(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
queryArgs.DefaultSortBy("first_name")
|
||||
// remap query args
|
||||
queryArgs.RemapOrderBy(recipientColumnByMap)
|
||||
// get recipients
|
||||
recipients, err := r.RecipientService.GetAll(
|
||||
g.Request.Context(),
|
||||
companyID,
|
||||
session,
|
||||
&repository.RecipientOption{
|
||||
QueryArgs: queryArgs,
|
||||
},
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, recipients)
|
||||
}
|
||||
|
||||
// GetByID gets a recipient by id
|
||||
func (r *Recipient) GetByID(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse id
|
||||
id, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// get recipient
|
||||
recipient, err := r.RecipientService.GetByID(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
id,
|
||||
&repository.RecipientOption{
|
||||
WithCompany: true,
|
||||
WithGroups: true,
|
||||
},
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, recipient)
|
||||
}
|
||||
|
||||
// GetStatsByID gets a recipient campaign stats by id
|
||||
func (r *Recipient) GetStatsByID(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse id
|
||||
id, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// get recipient stats
|
||||
stats, err := r.RecipientService.GetStatsByID(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
id,
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, stats)
|
||||
}
|
||||
|
||||
// UpdateByID updates a recipient by id
|
||||
func (r *Recipient) UpdateByID(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
id, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req model.Recipient
|
||||
if ok := r.handleParseRequest(g, &req); !ok {
|
||||
return
|
||||
}
|
||||
err := r.RecipientService.UpdateByID(
|
||||
g.Request.Context(),
|
||||
session,
|
||||
id,
|
||||
&req,
|
||||
)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, gin.H{})
|
||||
}
|
||||
|
||||
// Import imports recipients
|
||||
func (r *Recipient) Import(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse request
|
||||
var req struct {
|
||||
Recipients []*model.Recipient `json:"recipients"`
|
||||
CompanyID *uuid.UUID `json:"companyID"`
|
||||
IgnoreOverwriteEmptyFields nullable.Nullable[bool] `json:"ignoreOverwriteEmptyFields"`
|
||||
}
|
||||
if ok := r.handleParseRequest(g, &req); !ok {
|
||||
return
|
||||
}
|
||||
// IgnoreOverwriteEmptyFields default value is true
|
||||
if !req.IgnoreOverwriteEmptyFields.IsSpecified() || req.IgnoreOverwriteEmptyFields.IsNull() {
|
||||
req.IgnoreOverwriteEmptyFields = nullable.NewNullableWithValue(true)
|
||||
}
|
||||
_, err := r.RecipientService.Import(
|
||||
g,
|
||||
session,
|
||||
req.Recipients,
|
||||
req.IgnoreOverwriteEmptyFields.MustGet(),
|
||||
req.CompanyID,
|
||||
)
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, &gin.H{})
|
||||
}
|
||||
|
||||
// DeleteByID deletes a recipient by id
|
||||
func (r *Recipient) DeleteByID(g *gin.Context) {
|
||||
session, _, ok := r.handleSession(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// parse id
|
||||
id, ok := r.handleParseIDParam(g)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// delete recipient
|
||||
err := r.RecipientService.DeleteByID(g, session, id)
|
||||
// handle response
|
||||
if ok := r.handleErrors(g, err); !ok {
|
||||
return
|
||||
}
|
||||
r.Response.OK(g, gin.H{})
|
||||
}
|
||||
Reference in New Issue
Block a user