github.com/merlinepedra/gopphish-attack@v0.9.0/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 := []Target{}
   230  	ts, err := GetTargets(g.Id)
   231  	if err != nil {
   232  		log.WithFields(logrus.Fields{
   233  			"group_id": g.Id,
   234  		}).Error("Error getting targets from group")
   235  		return err
   236  	}
   237  	// Preload the caches
   238  	cacheNew := make(map[string]int64, len(g.Targets))
   239  	for _, t := range g.Targets {
   240  		cacheNew[t.Email] = t.Id
   241  	}
   242  
   243  	cacheExisting := make(map[string]int64, len(ts))
   244  	for _, t := range ts {
   245  		cacheExisting[t.Email] = t.Id
   246  	}
   247  
   248  	tx := db.Begin()
   249  	// Check existing targets, removing any that are no longer in the group.
   250  	for _, t := range ts {
   251  		if _, ok := cacheNew[t.Email]; ok {
   252  			continue
   253  		}
   254  
   255  		// If the target does not exist in the group any longer, we delete it
   256  		err := tx.Where("group_id=? and target_id=?", g.Id, t.Id).Delete(&GroupTarget{}).Error
   257  		if err != nil {
   258  			tx.Rollback()
   259  			log.WithFields(logrus.Fields{
   260  				"email": t.Email,
   261  			}).Error("Error deleting email")
   262  		}
   263  	}
   264  	// Add any targets that are not in the database yet.
   265  	for _, nt := range g.Targets {
   266  		// If the target already exists in the database, we should just update
   267  		// the record with the latest information.
   268  		if id, ok := cacheExisting[nt.Email]; ok {
   269  			nt.Id = id
   270  			err = UpdateTarget(tx, nt)
   271  			if err != nil {
   272  				log.Error(err)
   273  				tx.Rollback()
   274  				return err
   275  			}
   276  			continue
   277  		}
   278  		// Otherwise, add target if not in database
   279  		err = insertTargetIntoGroup(tx, nt, g.Id)
   280  		if err != nil {
   281  			log.Error(err)
   282  			tx.Rollback()
   283  			return err
   284  		}
   285  	}
   286  	err = tx.Save(g).Error
   287  	if err != nil {
   288  		log.Error(err)
   289  		return err
   290  	}
   291  	err = tx.Commit().Error
   292  	if err != nil {
   293  		tx.Rollback()
   294  		return err
   295  	}
   296  	return nil
   297  }
   298  
   299  // DeleteGroup deletes a given group by group ID and user ID
   300  func DeleteGroup(g *Group) error {
   301  	// Delete all the group_targets entries for this group
   302  	err := db.Where("group_id=?", g.Id).Delete(&GroupTarget{}).Error
   303  	if err != nil {
   304  		log.Error(err)
   305  		return err
   306  	}
   307  	// Delete the group itself
   308  	err = db.Delete(g).Error
   309  	if err != nil {
   310  		log.Error(err)
   311  		return err
   312  	}
   313  	return err
   314  }
   315  
   316  func insertTargetIntoGroup(tx *gorm.DB, t Target, gid int64) error {
   317  	if _, err := mail.ParseAddress(t.Email); err != nil {
   318  		log.WithFields(logrus.Fields{
   319  			"email": t.Email,
   320  		}).Error("Invalid email")
   321  		return err
   322  	}
   323  	err := tx.Where(t).FirstOrCreate(&t).Error
   324  	if err != nil {
   325  		log.WithFields(logrus.Fields{
   326  			"email": t.Email,
   327  		}).Error(err)
   328  		return err
   329  	}
   330  	err = tx.Save(&GroupTarget{GroupId: gid, TargetId: t.Id}).Error
   331  	if err != nil {
   332  		log.Error(err)
   333  		return err
   334  	}
   335  	if err != nil {
   336  		log.WithFields(logrus.Fields{
   337  			"email": t.Email,
   338  		}).Error("Error adding many-many mapping")
   339  		return err
   340  	}
   341  	return nil
   342  }
   343  
   344  // UpdateTarget updates the given target information in the database.
   345  func UpdateTarget(tx *gorm.DB, target Target) error {
   346  	targetInfo := map[string]interface{}{
   347  		"first_name": target.FirstName,
   348  		"last_name":  target.LastName,
   349  		"position":   target.Position,
   350  	}
   351  	err := tx.Model(&target).Where("id = ?", target.Id).Updates(targetInfo).Error
   352  	if err != nil {
   353  		log.WithFields(logrus.Fields{
   354  			"email": target.Email,
   355  		}).Error("Error updating target information")
   356  	}
   357  	return err
   358  }
   359  
   360  // GetTargets performs a many-to-many select to get all the Targets for a Group
   361  func GetTargets(gid int64) ([]Target, error) {
   362  	ts := []Target{}
   363  	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
   364  	return ts, err
   365  }