package sync import ( "bytes" "crypto/tls" "fmt" "log" "strings" "github.com/remogatto/slicediff" "github.com/sethvargo/go-password/password" gomail "gopkg.in/gomail.v2" ldap "gopkg.in/ldap.v2" "text/template" karmen_client "gogs.carducci-dante.gov.it/karmen/client" "gogs.carducci-dante.gov.it/karmen/core/config" "gogs.carducci-dante.gov.it/karmen/core/orm" karmen_ldap "gogs.carducci-dante.gov.it/karmen/ldap" tpl_util "gogs.carducci-dante.gov.it/karmen/util/template" ) type entriesToDN []*ldap.Entry func (entries entriesToDN) Convert() (result []string) { for _, entry := range entries { result = append( result, entry.DN, ) } return } type usersToDNConverter []orm.User func (users usersToDNConverter) Convert() (result []string) { for _, user := range users { result = append( result, user.DN(), ) } return } type usersToUsernamesConverter []orm.User func (users usersToUsernamesConverter) Convert() (result []string) { for _, user := range users { result = append( result, user.Username(), ) } return } type SliceToStringSlicer []string func (slice SliceToStringSlicer) Convert() (result []string) { return slice } type SyncJob struct { conf *config.ConfigT } func NewSyncJob(conf *config.ConfigT) *SyncJob { return &SyncJob{conf} } func logf(fmt string, args ...interface{}) { if config.Config.Sync.Verbose { log.Printf(fmt, args...) } } func (syncJob *SyncJob) sendMail(user orm.User, tpl *template.Template) error { var buf bytes.Buffer err := tpl.Execute(&buf, user) if err != nil { return err } if !syncJob.conf.Sync.SafeRun { logf("SEND user credential to %s...", user.GetAltEmail()) m := gomail.NewMessage() m.SetHeader("From", syncJob.conf.Smtp.From) m.SetHeader("To", user.GetAltEmail()) m.SetHeader("Cc", syncJob.conf.Smtp.Cc) m.SetHeader("Subject", fmt.Sprintf("Attivazione dell'utenza %s per l'accesso ai servizi informatici del Liceo \"Carducci-Dante\" di Trieste", user.CompleteName())) m.SetBody("text/plain", buf.String()) d := gomail.NewDialer(syncJob.conf.Smtp.Host, syncJob.conf.Smtp.Port, syncJob.conf.Smtp.Username, syncJob.conf.Smtp.Password) d.TLSConfig = &tls.Config{InsecureSkipVerify: true} if err := d.DialAndSend(m); err != nil { return err } } else { logf("Credentials would be sent to %s, new password is %s", user.GetAltEmail(), user.GetPlainPassword()) } return nil } func (syncJob *SyncJob) SyncUsers(ldapClient *karmen_ldap.Client, karmenClient *karmen_client.Client, entries []*ldap.Entry, users []orm.User) error { mailTpl, err := tpl_util.LoadTextTemplate("cron/sync/mail.tpl") if err != nil { log.Println(err) } actions := slicediff.Diff(entriesToDN(entries).Convert, usersToDNConverter(users).Convert) toBeAdded := make([]orm.User, 0) toBeRemoved := make([]string, 0) toBeUpdated := make([]orm.User, 0) for _, a := range actions { switch a.Type { case slicediff.Remove: toBeRemoved = append(toBeRemoved, entries[a.Id].DN) case slicediff.Add: user := users[a.Id] if !user.GetExclude() { toBeAdded = append(toBeAdded, user) } case slicediff.Update: user := users[a.Id] if !user.GetExclude() && user.GetRegenerate() { toBeUpdated = append(toBeUpdated, user) } } } // Add if !syncJob.conf.Sync.SafeRun { logf("ADDING %d users", len(toBeAdded)) for _, user := range toBeAdded { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } user.SetPlainPassword(password) err = ldapClient.AddUser(user) if err != nil { return err } logf("ADD teacher %s", user.CompleteName()) if user.GetRegenerate() { err = syncJob.sendMail(user, mailTpl) if err != nil { return err } user.SetRegenerate(false) err = karmenClient.UpdateUser(user) if err != nil { return err } } } } else { if len(toBeAdded) == 0 { log.Println("No users WOULD BE added.") } else { logf("%d users WOULD BE added...", len(toBeAdded)) for _, teacher := range toBeAdded { log.Println(teacher.CompleteName()) if teacher.GetRegenerate() { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } teacher.SetPlainPassword(password) err = syncJob.sendMail(teacher, mailTpl) if err != nil { return err } } } } } // Remove if !syncJob.conf.Sync.SafeRun { if len(toBeRemoved) > 0 { logf("%d will be REMOVED...", len(toBeRemoved)) } for _, entry := range toBeRemoved { err := ldapClient.DeleteByDN(entry) if err != nil { return err } logf("REMOVE %s", entry) } } else { if len(toBeRemoved) > 0 { logf("%d users WOULD BE removed", len(toBeRemoved)) } for _, entry := range toBeRemoved { log.Println(entry) } } // Update if !syncJob.conf.Sync.SafeRun { if len(toBeUpdated) > 0 { logf("UPDATING %d users", len(toBeUpdated)) } for _, user := range toBeUpdated { if user.GetRegenerate() { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } user.SetPlainPassword(password) err = syncJob.sendMail(user, mailTpl) if err != nil { return err } user.SetRegenerate(false) err = karmenClient.UpdateUser(user) if err != nil { return err } err = ldapClient.UpdateUserPassword(user) if err != nil { return err } } } } else { if len(toBeRemoved) > 0 { logf("%d users WOULD BE updated...", len(toBeUpdated)) } for _, teacher := range toBeUpdated { log.Println(teacher.CompleteName()) } } return nil } func (syncJob *SyncJob) syncGroup(ldapClient *karmen_ldap.Client, groupDN string, users []orm.User) error { actions := make(map[string]*slicediff.Action) entries, err := ldapClient.GroupMembers(groupDN) if err != nil { return err } values := entries[0].Attributes[0].Values if strings.Contains(groupDN, "Mailing Lists") { actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToDNConverter(users).Convert) } else { actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToUsernamesConverter(users).Convert) } toBeAdded := make([]orm.User, 0) toBeRemoved := make([]string, 0) for _, a := range actions { switch a.Type { case slicediff.Remove: toBeRemoved = append(toBeRemoved, values[a.Id]) case slicediff.Add: user := users[a.Id] if !user.GetExclude() { toBeAdded = append(toBeAdded, user) } } } if !syncJob.conf.Sync.SafeRun { if len(toBeAdded) > 0 { logf("ADDING %d teachers to %s group", len(toBeAdded), groupDN) } for _, user := range toBeAdded { log.Println(user.CompleteName()) err = ldapClient.AddUserToGroup(user, groupDN) if err != nil { return err } } } else { if len(toBeAdded) > 0 { logf("%d users WOULD BE ADDED to %s group", len(toBeAdded), groupDN) } for _, teacher := range toBeAdded { log.Println(teacher.CompleteName()) } } if !syncJob.conf.Sync.SafeRun { if len(toBeRemoved) > 0 { logf("REMOVING %d users from %s group...", len(toBeRemoved), groupDN) } for _, value := range toBeRemoved { logf("REMOVE %s from %s group.", value, groupDN) ldapClient.RemoveUserFromGroupByMemberValue(value, groupDN) } } else { if len(toBeRemoved) > 0 { logf("%d users WOULD BE REMOVED FROM %s group", len(toBeRemoved), groupDN) } for _, entry := range toBeRemoved { log.Println(entry) } } return nil } func (syncJob *SyncJob) Run() { if syncJob.conf.Sync.SafeRun { log.Println("Running in SAFE MODE...") } logf("Connecting to karmen at %s...", syncJob.conf.Url) karmenClient, err := karmen_client.Dial(syncJob.conf.Url, syncJob.conf.Admin.Username, syncJob.conf.Admin.Password) if err != nil { log.Println(err) } teachers, err := karmenClient.GetTeachers() if err != nil { log.Println(err) } logf("Connecting to LDAP at %s...", syncJob.conf.Ldap.Host) ldapClient, err := karmen_ldap.NewClient(syncJob.conf.Ldap.Host, syncJob.conf) if err != nil { log.Println(err) } log.Println("Retrieving teachers from LDAP...") entries, err := ldapClient.Users("ou=Docenti,ou=Persone,dc=carducci-dante,dc=gov,dc=it") if err != nil { log.Println(err) } log.Println("Retrieving Departments...") departments, err := karmenClient.GetDepartments() if err != nil { log.Println(err) } users := make([]orm.User, 0) for _, teacher := range teachers { users = append(users, teacher) } log.Println("Sync teachers...") if err := syncJob.SyncUsers(ldapClient, karmenClient, entries, users); err != nil { panic(err) } log.Println("Sync 'Docenti' group...") if err := syncJob.syncGroup(ldapClient, "cn=Docenti", users); err != nil { panic(err) } log.Println("Sync 'Tutti i docenti' ML...") if err := syncJob.syncGroup(ldapClient, "cn=Tutti i docenti,ou=Mailing Lists", users); err != nil { panic(err) } log.Println("Sync Departments and MLs...") for _, department := range departments { users := make([]orm.User, 0) for _, teacher := range department.Teachers { users = append(users, teacher) } group := fmt.Sprintf("cn=%s,ou=Mailing Lists", department.Name) err = syncJob.syncGroup(ldapClient, group, users) if err != nil { panic(err) } group = fmt.Sprintf("cn=%s,ou=Dipartimenti", department.Name) err = syncJob.syncGroup(ldapClient, group, users) if err != nil { panic(err) } } }