2014-03-25 03:31:33 +00:00
package models
import (
2015-02-21 06:11:22 +00:00
"errors"
2017-06-17 03:21:08 +00:00
"fmt"
2014-03-25 03:31:33 +00:00
"net/mail"
2020-10-23 16:56:00 +00:00
"strings"
2014-03-25 03:31:33 +00:00
"time"
2014-03-26 19:32:48 +00:00
2018-05-04 00:07:41 +00:00
log "github.com/gophish/gophish/logger"
2014-03-26 19:32:48 +00:00
"github.com/jinzhu/gorm"
2018-05-04 00:07:41 +00:00
"github.com/sirupsen/logrus"
2014-03-25 03:31:33 +00:00
)
2015-02-21 06:11:22 +00:00
// Group contains the fields needed for a user -> group mapping
// Groups contain 1..* Targets
2014-03-25 03:31:33 +00:00
type Group struct {
Id int64 ` json:"id" `
2014-03-27 18:19:57 +00:00
UserId int64 ` json:"-" `
2014-03-25 03:31:33 +00:00
Name string ` json:"name" `
2014-03-26 04:53:51 +00:00
ModifiedDate time . Time ` json:"modified_date" `
Targets [ ] Target ` json:"targets" sql:"-" `
}
2017-01-14 23:26:04 +00:00
// GroupSummaries is a struct representing the overview of Groups.
type GroupSummaries struct {
Total int64 ` json:"total" `
Groups [ ] GroupSummary ` json:"groups" `
}
// GroupSummary represents a summary of the Group model. The only
// difference is that, instead of listing the Targets (which could be expensive
// for large groups), it lists the target count.
type GroupSummary struct {
Id int64 ` json:"id" `
Name string ` json:"name" `
ModifiedDate time . Time ` json:"modified_date" `
NumTargets int64 ` json:"num_targets" `
}
2015-02-21 06:11:22 +00:00
// GroupTarget is used for a many-to-many relationship between 1..* Groups and 1..* Targets
2014-03-26 04:53:51 +00:00
type GroupTarget struct {
GroupId int64 ` json:"-" `
TargetId int64 ` json:"-" `
2014-03-25 03:31:33 +00:00
}
2015-02-21 06:11:22 +00:00
// Target contains the fields needed for individual targets specified by the user
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
2014-03-25 03:31:33 +00:00
type Target struct {
2020-10-23 16:56:00 +00:00
Id int64 ` json:"id" `
2018-06-09 02:20:52 +00:00
BaseRecipient
}
// BaseRecipient contains the fields for a single recipient. This is the base
// struct used in members of groups and campaign results.
type BaseRecipient struct {
Email string ` json:"email" `
2014-06-22 02:06:16 +00:00
FirstName string ` json:"first_name" `
LastName string ` json:"last_name" `
2015-02-21 17:36:08 +00:00
Position string ` json:"position" `
2014-03-25 03:31:33 +00:00
}
2020-10-23 16:56:00 +00:00
// DataTable is used to return a JSON object suitable for consumption by DataTables
// when using pagination
type DataTable struct {
Draw int64 ` json:"draw" `
RecordsTotal int64 ` json:"recordsTotal" `
RecordsFiltered int64 ` json:"recordsFiltered" `
Data [ ] interface { } ` json:"data" `
}
2018-06-09 02:20:52 +00:00
// FormatAddress returns the email address to use in the "To" header of the email
func ( r * BaseRecipient ) FormatAddress ( ) string {
addr := r . Email
if r . FirstName != "" && r . LastName != "" {
a := & mail . Address {
Name : fmt . Sprintf ( "%s %s" , r . FirstName , r . LastName ) ,
Address : r . Email ,
}
addr = a . String ( )
}
return addr
}
// FormatAddress returns the email address to use in the "To" header of the email
2017-06-17 03:21:08 +00:00
func ( t * Target ) FormatAddress ( ) string {
addr := t . Email
if t . FirstName != "" && t . LastName != "" {
a := & mail . Address {
Name : fmt . Sprintf ( "%s %s" , t . FirstName , t . LastName ) ,
Address : t . Email ,
}
addr = a . String ( )
}
return addr
}
2018-06-09 02:20:52 +00:00
// ErrEmailNotSpecified is thrown when no email is specified for the Target
2016-01-25 02:03:53 +00:00
var ErrEmailNotSpecified = errors . New ( "No email address specified" )
2015-02-21 06:11:22 +00:00
// ErrGroupNameNotSpecified is thrown when a group name is not specified
var ErrGroupNameNotSpecified = errors . New ( "Group name not specified" )
// ErrNoTargetsSpecified is thrown when no targets are specified by the user
var ErrNoTargetsSpecified = errors . New ( "No targets specified" )
// Validate performs validation on a group given by the user
func ( g * Group ) Validate ( ) error {
switch {
case g . Name == "" :
return ErrGroupNameNotSpecified
case len ( g . Targets ) == 0 :
return ErrNoTargetsSpecified
}
return nil
}
2014-03-25 03:31:33 +00:00
// GetGroups returns the groups owned by the given user.
func GetGroups ( uid int64 ) ( [ ] Group , error ) {
gs := [ ] Group { }
2014-03-27 18:19:57 +00:00
err := db . Where ( "user_id=?" , uid ) . Find ( & gs ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
return gs , err
}
2018-05-04 00:07:41 +00:00
for i := range gs {
2020-10-24 16:28:51 +00:00
gs [ i ] . Targets , err = GetTargets ( gs [ i ] . Id )
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
}
}
return gs , nil
}
2017-01-14 23:26:04 +00:00
// GetGroupSummaries returns the summaries for the groups
// created by the given uid.
func GetGroupSummaries ( uid int64 ) ( GroupSummaries , error ) {
gs := GroupSummaries { }
query := db . Table ( "groups" ) . Where ( "user_id=?" , uid )
err := query . Select ( "id, name, modified_date" ) . Scan ( & gs . Groups ) . Error
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2017-01-14 23:26:04 +00:00
return gs , err
}
for i := range gs . Groups {
query = db . Table ( "group_targets" ) . Where ( "group_id=?" , gs . Groups [ i ] . Id )
err = query . Count ( & gs . Groups [ i ] . NumTargets ) . Error
if err != nil {
return gs , err
}
}
gs . Total = int64 ( len ( gs . Groups ) )
return gs , nil
}
2014-03-25 03:31:33 +00:00
// GetGroup returns the group, if it exists, specified by the given id and user_id.
2020-10-24 16:28:51 +00:00
func GetGroup ( id int64 , uid int64 ) ( Group , error ) {
2014-03-25 03:31:33 +00:00
g := Group { }
2014-03-27 18:19:57 +00:00
err := db . Where ( "user_id=? and id=?" , uid , id ) . Find ( & g ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
return g , err
}
2020-10-24 16:28:51 +00:00
g . Targets , err = GetTargets ( g . Id )
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
}
return g , nil
}
2017-01-14 23:26:04 +00:00
// GetGroupSummary returns the summary for the requested group
func GetGroupSummary ( id int64 , uid int64 ) ( GroupSummary , error ) {
g := GroupSummary { }
query := db . Table ( "groups" ) . Where ( "user_id=? and id=?" , uid , id )
err := query . Select ( "id, name, modified_date" ) . Scan ( & g ) . Error
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2017-01-14 23:26:04 +00:00
return g , err
}
query = db . Table ( "group_targets" ) . Where ( "group_id=?" , id )
err = query . Count ( & g . NumTargets ) . Error
if err != nil {
return g , err
}
return g , nil
}
2014-03-25 03:31:33 +00:00
// GetGroupByName returns the group, if it exists, specified by the given name and user_id.
func GetGroupByName ( n string , uid int64 ) ( Group , error ) {
g := Group { }
2014-03-27 18:19:57 +00:00
err := db . Where ( "user_id=? and name=?" , uid , n ) . Find ( & g ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
return g , err
}
2020-10-24 16:28:51 +00:00
g . Targets , err = GetTargets ( g . Id )
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
}
2015-02-07 20:31:41 +00:00
return g , err
2014-03-25 03:31:33 +00:00
}
// PostGroup creates a new group in the database.
2014-03-27 18:19:57 +00:00
func PostGroup ( g * Group ) error {
2015-02-21 06:11:22 +00:00
if err := g . Validate ( ) ; err != nil {
return err
}
// Insert the group into the DB
2019-12-03 05:00:11 +00:00
tx := db . Begin ( )
err := tx . Save ( g ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2019-12-03 05:00:11 +00:00
tx . Rollback ( )
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
return err
}
for _ , t := range g . Targets {
2019-12-03 05:00:11 +00:00
err = insertTargetIntoGroup ( tx , t , g . Id )
if err != nil {
tx . Rollback ( )
log . Error ( err )
return err
}
}
err = tx . Commit ( ) . Error
if err != nil {
log . Error ( err )
tx . Rollback ( )
return err
2014-03-25 03:31:33 +00:00
}
return nil
}
// PutGroup updates the given group if found in the database.
2014-03-27 18:19:57 +00:00
func PutGroup ( g * Group ) error {
2015-08-05 05:23:05 +00:00
if err := g . Validate ( ) ; err != nil {
return err
}
2016-06-26 13:31:03 +00:00
// Fetch group's existing targets from database.
2020-10-24 16:28:51 +00:00
ts , err := GetTargets ( g . Id )
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . WithFields ( logrus . Fields {
"group_id" : g . Id ,
} ) . Error ( "Error getting targets from group" )
2014-03-25 03:31:33 +00:00
return err
}
2019-12-03 05:00:11 +00:00
// Preload the caches
cacheNew := make ( map [ string ] int64 , len ( g . Targets ) )
for _ , t := range g . Targets {
cacheNew [ t . Email ] = t . Id
}
cacheExisting := make ( map [ string ] int64 , len ( ts ) )
for _ , t := range ts {
cacheExisting [ t . Email ] = t . Id
}
tx := db . Begin ( )
2016-06-26 13:31:03 +00:00
// Check existing targets, removing any that are no longer in the group.
2014-03-25 03:31:33 +00:00
for _ , t := range ts {
2019-12-03 05:00:11 +00:00
if _ , ok := cacheNew [ t . Email ] ; ok {
continue
2014-03-25 03:31:33 +00:00
}
2019-12-03 05:00:11 +00:00
2014-03-25 03:31:33 +00:00
// If the target does not exist in the group any longer, we delete it
2019-12-03 05:00:11 +00:00
err := tx . Where ( "group_id=? and target_id=?" , g . Id , t . Id ) . Delete ( & GroupTarget { } ) . Error
if err != nil {
tx . Rollback ( )
log . WithFields ( logrus . Fields {
"email" : t . Email ,
} ) . Error ( "Error deleting email" )
2014-03-25 03:31:33 +00:00
}
}
2016-06-26 13:31:03 +00:00
// Add any targets that are not in the database yet.
2014-03-25 03:31:33 +00:00
for _ , nt := range g . Targets {
2019-12-03 05:00:11 +00:00
// If the target already exists in the database, we should just update
// the record with the latest information.
if id , ok := cacheExisting [ nt . Email ] ; ok {
nt . Id = id
err = UpdateTarget ( tx , nt )
if err != nil {
log . Error ( err )
tx . Rollback ( )
return err
2014-03-25 03:31:33 +00:00
}
2019-12-03 05:00:11 +00:00
continue
2014-03-25 03:31:33 +00:00
}
2019-12-03 05:00:11 +00:00
// Otherwise, add target if not in database
err = insertTargetIntoGroup ( tx , nt , g . Id )
if err != nil {
log . Error ( err )
tx . Rollback ( )
return err
2014-03-25 03:31:33 +00:00
}
}
2019-12-03 05:00:11 +00:00
err = tx . Save ( g ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-25 03:31:33 +00:00
return err
}
2019-12-03 05:00:11 +00:00
err = tx . Commit ( ) . Error
if err != nil {
tx . Rollback ( )
return err
}
2014-03-25 03:31:33 +00:00
return nil
}
2014-03-27 18:19:57 +00:00
// DeleteGroup deletes a given group by group ID and user ID
func DeleteGroup ( g * Group ) error {
// Delete all the group_targets entries for this group
err := db . Where ( "group_id=?" , g . Id ) . Delete ( & GroupTarget { } ) . Error
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-27 18:19:57 +00:00
return err
}
// Delete the group itself
err = db . Delete ( g ) . Error
if err != nil {
2018-05-04 00:07:41 +00:00
log . Error ( err )
2014-03-27 18:19:57 +00:00
return err
}
return err
}
2020-10-23 16:56:00 +00:00
// DeleteTarget deletes a single target from a group given by target ID
func DeleteTarget ( t * Target , gid int64 , uid int64 ) error {
targetOwner , err := GetTargetOwner ( t . Id )
if err != nil {
return err
}
if targetOwner != uid {
return errors . New ( "No such target id (wrong owner)" )
}
err = db . Delete ( t ) . Error
if err != nil {
return err
}
err = db . Where ( "target_id=?" , t . Id ) . Delete ( & GroupTarget { } ) . Error
if err != nil {
return err
}
// Update group modification date
2020-10-24 09:40:24 +00:00
err = db . Model ( & Group { } ) . Where ( "id=?" , gid ) . Update ( "ModifiedDate" , time . Now ( ) . UTC ( ) ) . Error
2020-10-23 16:56:00 +00:00
return err
}
// UpdateGroup updates a given group (without updating the targets)
// Note: I thought about putting this in the Group() function, but we'd have to skip the validation and have a boolean
// indicating we just want to rename the group.
func UpdateGroup ( g * Group ) error {
if g . Name == "" {
return ErrGroupNameNotSpecified
}
err := db . Save ( g ) . Error
return err
}
// AddTargetToGroup adds a single given target to a group by group ID
func AddTargetToGroup ( nt Target , gid int64 ) error {
// Check if target already exists in group
tmpt , err := GetTargetByEmail ( gid , nt . Email )
if err != nil {
return err
}
// If target exists in group, update it.
if len ( tmpt ) > 0 {
nt . Id = tmpt [ 0 ] . Id
err = UpdateTarget ( db , nt )
} else {
err = insertTargetIntoGroup ( db , nt , gid )
}
if err != nil {
return err
}
err = db . Model ( & Group { } ) . Where ( "id=?" , gid ) . Update ( "ModifiedDate" , time . Now ( ) . UTC ( ) ) . Error
return err
}
2019-12-03 05:00:11 +00:00
func insertTargetIntoGroup ( tx * gorm . DB , t Target , gid int64 ) error {
2018-12-15 21:42:32 +00:00
if _ , err := mail . ParseAddress ( t . Email ) ; err != nil {
2018-05-04 00:07:41 +00:00
log . WithFields ( logrus . Fields {
"email" : t . Email ,
} ) . Error ( "Invalid email" )
2014-03-25 03:31:33 +00:00
return err
}
2019-12-03 05:00:11 +00:00
err := tx . Where ( t ) . FirstOrCreate ( & t ) . Error
2014-03-25 03:31:33 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . WithFields ( logrus . Fields {
"email" : t . Email ,
2018-06-09 02:20:52 +00:00
} ) . Error ( err )
2014-03-25 03:31:33 +00:00
return err
}
2020-01-17 02:41:13 +00:00
err = tx . Save ( & GroupTarget { GroupId : gid , TargetId : t . Id } ) . Error
if err != nil {
log . Error ( err )
return err
2014-03-25 03:31:33 +00:00
}
if err != nil {
2018-05-04 00:07:41 +00:00
log . WithFields ( logrus . Fields {
"email" : t . Email ,
} ) . Error ( "Error adding many-many mapping" )
2014-03-25 03:31:33 +00:00
return err
}
return nil
}
2016-07-02 12:01:24 +00:00
// UpdateTarget updates the given target information in the database.
2019-12-03 05:00:11 +00:00
func UpdateTarget ( tx * gorm . DB , target Target ) error {
2016-07-02 12:01:24 +00:00
targetInfo := map [ string ] interface { } {
"first_name" : target . FirstName ,
"last_name" : target . LastName ,
"position" : target . Position ,
}
2019-12-03 05:00:11 +00:00
err := tx . Model ( & target ) . Where ( "id = ?" , target . Id ) . Updates ( targetInfo ) . Error
2016-07-02 12:01:24 +00:00
if err != nil {
2018-05-04 00:07:41 +00:00
log . WithFields ( logrus . Fields {
"email" : target . Email ,
} ) . Error ( "Error updating target information" )
2016-07-02 12:01:24 +00:00
}
return err
}
2015-02-21 06:11:22 +00:00
// GetTargets performs a many-to-many select to get all the Targets for a Group
2020-10-24 16:28:51 +00:00
func GetTargets ( gid int64 ) ( [ ] Target , error ) {
2014-03-26 04:53:51 +00:00
ts := [ ] Target { }
2020-10-24 16:28:51 +00:00
err := db . Table ( "targets" ) . Select ( "targets.id, targets.email, targets.first_name, targets.last_name, targets.position" ) . Joins ( "left join group_targets gt ON targets.id = gt.target_id" ) . Where ( "gt.group_id=?" , gid ) . Scan ( & ts ) . Error
return ts , err
}
// GetDataTable performs a many-to-many select to get all the Targets for a Group with supplied filters
// start, length, and search, order can be supplied, or -1, -1, "", "" to ignore
func GetDataTable ( gid int64 , start int64 , length int64 , search string , order string ) ( DataTable , error ) {
2020-10-23 16:56:00 +00:00
2020-10-24 16:28:51 +00:00
dt := DataTable { }
ts := [ ] Target { }
2020-10-23 16:56:00 +00:00
order = strings . TrimSpace ( order )
search = strings . TrimSpace ( search )
if order == "" {
order = "targets.first_name asc"
} else {
order = "targets." + order
}
2020-10-24 16:28:51 +00:00
// 1. Get the total number of targets in group:
err := db . Table ( "group_targets" ) . Where ( "group_id=?" , gid ) . Count ( & dt . RecordsTotal ) . Error
if err != nil {
return dt , err
}
// 2. Fetch targets, applying relevant start, length, search, and order paramters.
2020-10-23 16:56:00 +00:00
// TODO: Rather than having two queries create a partial query and include the search options. Haven't been able to figure out how yet.
if search != "" {
2020-10-24 16:28:51 +00:00
var count int64
2020-10-23 16:56:00 +00:00
search = "%" + search + "%"
2020-10-24 16:28:51 +00:00
// 2.1 Apply search filter
err = db . Order ( order ) . Table ( "targets" ) . Select ( "targets.id, targets.email, targets.first_name, targets.last_name, targets.position" ) . Joins ( "left join group_targets gt ON targets.id = gt.target_id" ) . Where ( "gt.group_id=?" , gid ) . Where ( "targets.first_name LIKE ? OR targets.last_name LIKE ? OR targets.email LIKE ? or targets.position LIKE ?" , search , search , search , search ) . Count ( & count ) . Offset ( start ) . Limit ( length ) . Scan ( & ts ) . Error
dt . RecordsFiltered = count // The number of results from applying the search filter (calculated before trimming down the results with offset and limit)
2020-10-24 09:40:24 +00:00
2020-10-23 16:56:00 +00:00
} else {
err = db . Order ( order ) . Table ( "targets" ) . Select ( "targets.id, targets.email, targets.first_name, targets.last_name, targets.position" ) . Joins ( "left join group_targets gt ON targets.id = gt.target_id" ) . Where ( "gt.group_id=?" , gid ) . Offset ( start ) . Limit ( length ) . Scan ( & ts ) . Error
2020-10-24 16:28:51 +00:00
dt . RecordsFiltered = dt . RecordsTotal
2020-10-23 16:56:00 +00:00
}
2020-10-24 09:40:24 +00:00
2020-10-24 16:28:51 +00:00
// 3. Insert targes into datatable struct
dt . Data = make ( [ ] interface { } , len ( ts ) ) // Pseudocode of 'dT.Data = g.Targets'. https://golang.org/doc/faq#convert_slice_of_interface
for i , v := range ts {
dt . Data [ i ] = v
}
return dt , err
2014-03-26 04:53:51 +00:00
}
2020-10-23 16:56:00 +00:00
// GetTargetByEmail gets a single target from a group by email address and group id
func GetTargetByEmail ( gid int64 , email string ) ( [ ] Target , error ) {
ts := [ ] Target { }
err := db . Table ( "targets" ) . Select ( "targets.id, targets.email, targets.first_name, targets.last_name, targets.position" ) . Joins ( "left join group_targets gt ON targets.id = gt.target_id" ) . Where ( "gt.group_id=?" , gid ) . Where ( "targets.email=?" , email ) . First ( & ts ) . Error
return ts , err
}
// GetTargetOwner returns the user id owner of a given target id
func GetTargetOwner ( tid int64 ) ( int64 , error ) {
g := Group { }
err := db . Table ( "groups" ) . Select ( "groups.user_id" ) . Joins ( "left join group_targets on group_targets.group_id = groups.id" ) . Where ( "group_targets.target_id = ?" , tid ) . Scan ( & g ) . Error
return g . UserId , err
}