github.com/amar224/phishing-tool@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 }