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 }