github.com/gophish/gophish@v0.12.2-0.20230915144530-8e7929441393/models/group.go (about)

     1  package models
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/mail"
     7  	"time"
     8  
     9  	log "github.com/gophish/gophish/logger"
    10  	"github.com/jinzhu/gorm"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  // Group contains the fields needed for a user -> group mapping
    15  // Groups contain 1..* Targets
    16  type Group struct {
    17  	Id           int64     `json:"id"`
    18  	UserId       int64     `json:"-"`
    19  	Name         string    `json:"name"`
    20  	ModifiedDate time.Time `json:"modified_date"`
    21  	Targets      []Target  `json:"targets" sql:"-"`
    22  }
    23  
    24  // GroupSummaries is a struct representing the overview of Groups.
    25  type GroupSummaries struct {
    26  	Total  int64          `json:"total"`
    27  	Groups []GroupSummary `json:"groups"`
    28  }
    29  
    30  // GroupSummary represents a summary of the Group model. The only
    31  // difference is that, instead of listing the Targets (which could be expensive
    32  // for large groups), it lists the target count.
    33  type GroupSummary struct {
    34  	Id           int64     `json:"id"`
    35  	Name         string    `json:"name"`
    36  	ModifiedDate time.Time `json:"modified_date"`
    37  	NumTargets   int64     `json:"num_targets"`
    38  }
    39  
    40  // GroupTarget is used for a many-to-many relationship between 1..* Groups and 1..* Targets
    41  type GroupTarget struct {
    42  	GroupId  int64 `json:"-"`
    43  	TargetId int64 `json:"-"`
    44  }
    45  
    46  // Target contains the fields needed for individual targets specified by the user
    47  // Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
    48  type Target struct {
    49  	Id int64 `json:"-"`
    50  	BaseRecipient
    51  }
    52  
    53  // BaseRecipient contains the fields for a single recipient. This is the base
    54  // struct used in members of groups and campaign results.
    55  type BaseRecipient struct {
    56  	Email     string `json:"email"`
    57  	FirstName string `json:"first_name"`
    58  	LastName  string `json:"last_name"`
    59  	Position  string `json:"position"`
    60  }
    61  
    62  // FormatAddress returns the email address to use in the "To" header of the email
    63  func (r *BaseRecipient) FormatAddress() string {
    64  	addr := r.Email
    65  	if r.FirstName != "" && r.LastName != "" {
    66  		a := &mail.Address{
    67  			Name:    fmt.Sprintf("%s %s", r.FirstName, r.LastName),
    68  			Address: r.Email,
    69  		}
    70  		addr = a.String()
    71  	}
    72  	return addr
    73  }
    74  
    75  // FormatAddress returns the email address to use in the "To" header of the email
    76  func (t *Target) FormatAddress() string {
    77  	addr := t.Email
    78  	if t.FirstName != "" && t.LastName != "" {
    79  		a := &mail.Address{
    80  			Name:    fmt.Sprintf("%s %s", t.FirstName, t.LastName),
    81  			Address: t.Email,
    82  		}
    83  		addr = a.String()
    84  	}
    85  	return addr
    86  }
    87  
    88  // ErrEmailNotSpecified is thrown when no email is specified for the Target
    89  var ErrEmailNotSpecified = errors.New("No email address specified")
    90  
    91  // ErrGroupNameNotSpecified is thrown when a group name is not specified
    92  var ErrGroupNameNotSpecified = errors.New("Group name not specified")
    93  
    94  // ErrNoTargetsSpecified is thrown when no targets are specified by the user
    95  var ErrNoTargetsSpecified = errors.New("No targets specified")
    96  
    97  // Validate performs validation on a group given by the user
    98  func (g *Group) Validate() error {
    99  	switch {
   100  	case g.Name == "":
   101  		return ErrGroupNameNotSpecified
   102  	case len(g.Targets) == 0:
   103  		return ErrNoTargetsSpecified
   104  	}
   105  	return nil
   106  }
   107  
   108  // GetGroups returns the groups owned by the given user.
   109  func GetGroups(uid int64) ([]Group, error) {
   110  	gs := []Group{}
   111  	err := db.Where("user_id=?", uid).Find(&gs).Error
   112  	if err != nil {
   113  		log.Error(err)
   114  		return gs, err
   115  	}
   116  	for i := range gs {
   117  		gs[i].Targets, err = GetTargets(gs[i].Id)
   118  		if err != nil {
   119  			log.Error(err)
   120  		}
   121  	}
   122  	return gs, nil
   123  }
   124  
   125  // GetGroupSummaries returns the summaries for the groups
   126  // created by the given uid.
   127  func GetGroupSummaries(uid int64) (GroupSummaries, error) {
   128  	gs := GroupSummaries{}
   129  	query := db.Table("groups").Where("user_id=?", uid)
   130  	err := query.Select("id, name, modified_date").Scan(&gs.Groups).Error
   131  	if err != nil {
   132  		log.Error(err)
   133  		return gs, err
   134  	}
   135  	for i := range gs.Groups {
   136  		query = db.Table("group_targets").Where("group_id=?", gs.Groups[i].Id)
   137  		err = query.Count(&gs.Groups[i].NumTargets).Error
   138  		if err != nil {
   139  			return gs, err
   140  		}
   141  	}
   142  	gs.Total = int64(len(gs.Groups))
   143  	return gs, nil
   144  }
   145  
   146  // GetGroup returns the group, if it exists, specified by the given id and user_id.
   147  func GetGroup(id int64, uid int64) (Group, error) {
   148  	g := Group{}
   149  	err := db.Where("user_id=? and id=?", uid, id).Find(&g).Error
   150  	if err != nil {
   151  		log.Error(err)
   152  		return g, err
   153  	}
   154  	g.Targets, err = GetTargets(g.Id)
   155  	if err != nil {
   156  		log.Error(err)
   157  	}
   158  	return g, nil
   159  }
   160  
   161  // GetGroupSummary returns the summary for the requested group
   162  func GetGroupSummary(id int64, uid int64) (GroupSummary, error) {
   163  	g := GroupSummary{}
   164  	query := db.Table("groups").Where("user_id=? and id=?", uid, id)
   165  	err := query.Select("id, name, modified_date").Scan(&g).Error
   166  	if err != nil {
   167  		log.Error(err)
   168  		return g, err
   169  	}
   170  	query = db.Table("group_targets").Where("group_id=?", id)
   171  	err = query.Count(&g.NumTargets).Error
   172  	if err != nil {
   173  		return g, err
   174  	}
   175  	return g, nil
   176  }
   177  
   178  // GetGroupByName returns the group, if it exists, specified by the given name and user_id.
   179  func GetGroupByName(n string, uid int64) (Group, error) {
   180  	g := Group{}
   181  	err := db.Where("user_id=? and name=?", uid, n).Find(&g).Error
   182  	if err != nil {
   183  		log.Error(err)
   184  		return g, err
   185  	}
   186  	g.Targets, err = GetTargets(g.Id)
   187  	if err != nil {
   188  		log.Error(err)
   189  	}
   190  	return g, err
   191  }
   192  
   193  // PostGroup creates a new group in the database.
   194  func PostGroup(g *Group) error {
   195  	if err := g.Validate(); err != nil {
   196  		return err
   197  	}
   198  	// Insert the group into the DB
   199  	tx := db.Begin()
   200  	err := tx.Save(g).Error
   201  	if err != nil {
   202  		tx.Rollback()
   203  		log.Error(err)
   204  		return err
   205  	}
   206  	for _, t := range g.Targets {
   207  		err = insertTargetIntoGroup(tx, t, g.Id)
   208  		if err != nil {
   209  			tx.Rollback()
   210  			log.Error(err)
   211  			return err
   212  		}
   213  	}
   214  	err = tx.Commit().Error
   215  	if err != nil {
   216  		log.Error(err)
   217  		tx.Rollback()
   218  		return err
   219  	}
   220  	return nil
   221  }
   222  
   223  // PutGroup updates the given group if found in the database.
   224  func PutGroup(g *Group) error {
   225  	if err := g.Validate(); err != nil {
   226  		return err
   227  	}
   228  	// Fetch group's existing targets from database.
   229  	ts, err := GetTargets(g.Id)
   230  	if err != nil {
   231  		log.WithFields(logrus.Fields{
   232  			"group_id": g.Id,
   233  		}).Error("Error getting targets from group")
   234  		return err
   235  	}
   236  	// Preload the caches
   237  	cacheNew := make(map[string]int64, len(g.Targets))
   238  	for _, t := range g.Targets {
   239  		cacheNew[t.Email] = t.Id
   240  	}
   241  
   242  	cacheExisting := make(map[string]int64, len(ts))
   243  	for _, t := range ts {
   244  		cacheExisting[t.Email] = t.Id
   245  	}
   246  
   247  	tx := db.Begin()
   248  	// Check existing targets, removing any that are no longer in the group.
   249  	for _, t := range ts {
   250  		if _, ok := cacheNew[t.Email]; ok {
   251  			continue
   252  		}
   253  
   254  		// If the target does not exist in the group any longer, we delete it
   255  		err := tx.Where("group_id=? and target_id=?", g.Id, t.Id).Delete(&GroupTarget{}).Error
   256  		if err != nil {
   257  			tx.Rollback()
   258  			log.WithFields(logrus.Fields{
   259  				"email": t.Email,
   260  			}).Error("Error deleting email")
   261  		}
   262  	}
   263  	// Add any targets that are not in the database yet.
   264  	for _, nt := range g.Targets {
   265  		// If the target already exists in the database, we should just update
   266  		// the record with the latest information.
   267  		if id, ok := cacheExisting[nt.Email]; ok {
   268  			nt.Id = id
   269  			err = UpdateTarget(tx, nt)
   270  			if err != nil {
   271  				log.Error(err)
   272  				tx.Rollback()
   273  				return err
   274  			}
   275  			continue
   276  		}
   277  		// Otherwise, add target if not in database
   278  		err = insertTargetIntoGroup(tx, nt, g.Id)
   279  		if err != nil {
   280  			log.Error(err)
   281  			tx.Rollback()
   282  			return err
   283  		}
   284  	}
   285  	err = tx.Save(g).Error
   286  	if err != nil {
   287  		log.Error(err)
   288  		return err
   289  	}
   290  	err = tx.Commit().Error
   291  	if err != nil {
   292  		tx.Rollback()
   293  		return err
   294  	}
   295  	return nil
   296  }
   297  
   298  // DeleteGroup deletes a given group by group ID and user ID
   299  func DeleteGroup(g *Group) error {
   300  	// Delete all the group_targets entries for this group
   301  	err := db.Where("group_id=?", g.Id).Delete(&GroupTarget{}).Error
   302  	if err != nil {
   303  		log.Error(err)
   304  		return err
   305  	}
   306  	// Delete the group itself
   307  	err = db.Delete(g).Error
   308  	if err != nil {
   309  		log.Error(err)
   310  		return err
   311  	}
   312  	return err
   313  }
   314  
   315  func insertTargetIntoGroup(tx *gorm.DB, t Target, gid int64) error {
   316  	if _, err := mail.ParseAddress(t.Email); err != nil {
   317  		log.WithFields(logrus.Fields{
   318  			"email": t.Email,
   319  		}).Error("Invalid email")
   320  		return err
   321  	}
   322  	err := tx.Where(t).FirstOrCreate(&t).Error
   323  	if err != nil {
   324  		log.WithFields(logrus.Fields{
   325  			"email": t.Email,
   326  		}).Error(err)
   327  		return err
   328  	}
   329  	err = tx.Save(&GroupTarget{GroupId: gid, TargetId: t.Id}).Error
   330  	if err != nil {
   331  		log.Error(err)
   332  		return err
   333  	}
   334  	if err != nil {
   335  		log.WithFields(logrus.Fields{
   336  			"email": t.Email,
   337  		}).Error("Error adding many-many mapping")
   338  		return err
   339  	}
   340  	return nil
   341  }
   342  
   343  // UpdateTarget updates the given target information in the database.
   344  func UpdateTarget(tx *gorm.DB, target Target) error {
   345  	targetInfo := map[string]interface{}{
   346  		"first_name": target.FirstName,
   347  		"last_name":  target.LastName,
   348  		"position":   target.Position,
   349  	}
   350  	err := tx.Model(&target).Where("id = ?", target.Id).Updates(targetInfo).Error
   351  	if err != nil {
   352  		log.WithFields(logrus.Fields{
   353  			"email": target.Email,
   354  		}).Error("Error updating target information")
   355  	}
   356  	return err
   357  }
   358  
   359  // GetTargets performs a many-to-many select to get all the Targets for a Group
   360  func GetTargets(gid int64) ([]Target, error) {
   361  	ts := []Target{}
   362  	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
   363  	return ts, err
   364  }