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 TeachersDsts []*ldap.Entry type TeachersSrcs []*orm.Teacher type TeachersMLSrcs []*orm.Teacher func (d TeachersDsts) Strings() (result []string) { for _, entry := range d { result = append( result, entry.DN, ) } return } func (s TeachersSrcs) Strings() (result []string) { for _, teacher := range s { result = append( result, fmt.Sprintf("cn=%s %s,%s", teacher.Name, teacher.Surname, "ou=Docenti,ou=Persone,dc=carducci-dante,dc=gov,dc=it"), ) } return } type TeachersGroupDsts []string type TeachersGroupSrcs []*orm.Teacher func (d TeachersGroupDsts) Strings() (result []string) { return d } func (s TeachersGroupSrcs) Strings() (result []string) { for _, teacher := range s { result = append( result, teacher.Username(), ) } return } type TeachersMLDsts []*orm.Teacher func (s TeachersMLDsts) Strings() (result []string) { for _, teacher := range s { result = append( result, fmt.Sprintf("cn=%s %s,%s", teacher.Name, teacher.Surname, "ou=Docenti,ou=Persone,dc=carducci-dante,dc=gov,dc=it"), ) } return } type SyncJob struct { conf *config.ConfigT } func NewSyncJob(conf *config.ConfigT) *SyncJob { return &SyncJob{conf} } func (syncJob *SyncJob) sendMail(teacher *orm.Teacher, tpl *template.Template) error { var buf bytes.Buffer err := tpl.Execute(&buf, teacher) if err != nil { return err } if !syncJob.conf.Sync.SafeRun { log.Printf("Invio le credenziali a %s", teacher.AltEmail) m := gomail.NewMessage() m.SetHeader("From", syncJob.conf.Smtp.From) m.SetHeader("To", teacher.AltEmail) 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", teacher.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 { log.Printf("Credentials would be sent to %s, new password is %s", teacher.AltEmail, teacher.PlainPassword) } return nil } func (syncJob *SyncJob) syncTeachers(ldapClient *karmen_ldap.Client, karmenClient *karmen_client.Client, entries []*ldap.Entry, teachers []*orm.Teacher) error { mailTpl, err := tpl_util.LoadTextTemplate("cron/sync/mail.tpl") if err != nil { log.Println(err) } actions := slicediff.Diff(TeachersDsts(entries), TeachersSrcs(teachers)) toBeAdded := make([]*orm.Teacher, 0) toBeRemoved := make([]string, 0) toBeUpdated := make([]*orm.Teacher, 0) for _, a := range actions { switch a.Type { case slicediff.Remove: toBeRemoved = append(toBeRemoved, entries[a.Id].DN) case slicediff.Add: teacher := teachers[a.Id] if !teacher.Exclude { toBeAdded = append(toBeAdded, teacher) } case slicediff.Update: teacher := teachers[a.Id] if !teacher.Exclude && teacher.Regenerate { toBeUpdated = append(toBeUpdated, teacher) } } } // Add if !syncJob.conf.Sync.SafeRun { log.Printf("ADDING %d users", len(toBeAdded)) for _, teacher := range toBeAdded { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } teacher.PlainPassword = password err = ldapClient.AddTeacher(teacher) if err != nil { return err } log.Printf("ADD teacher %s", teacher.CompleteName()) if teacher.Regenerate { err = syncJob.sendMail(teacher, mailTpl) if err != nil { return err } teacher.Regenerate = false err = karmenClient.UpdateTeacher(teacher) if err != nil { return err } } } } else { if len(toBeAdded) == 0 { log.Println("No users WOULD BE added.") } else { log.Printf("%d users WOULD BE added...", len(toBeAdded)) for _, teacher := range toBeAdded { log.Println(teacher.CompleteName()) if teacher.Regenerate { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } teacher.PlainPassword = password err = syncJob.sendMail(teacher, mailTpl) if err != nil { return err } } } } } // Remove if !syncJob.conf.Sync.SafeRun { if len(toBeRemoved) > 0 { log.Printf("%d will be REMOVED...", len(toBeRemoved)) } for _, entry := range toBeRemoved { err := ldapClient.DeleteByDN(entry) if err != nil { return err } log.Printf("REMOVE %s", entry) } } else { if len(toBeRemoved) > 0 { log.Printf("%d users WOULD BE removed", len(toBeRemoved)) } for _, entry := range toBeRemoved { log.Println(entry) } } // Update if !syncJob.conf.Sync.SafeRun { if len(toBeUpdated) > 0 { log.Printf("UPDATING %d users", len(toBeUpdated)) } for _, teacher := range toBeUpdated { if teacher.Regenerate { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return err } teacher.PlainPassword = password err = syncJob.sendMail(teacher, mailTpl) if err != nil { return err } teacher.Regenerate = false err = karmenClient.UpdateTeacher(teacher) if err != nil { return err } err = ldapClient.UpdateTeacherPassword(teacher, teacher.PlainPassword) if err != nil { return err } } } } else { if len(toBeRemoved) > 0 { log.Printf("%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, teachers []*orm.Teacher) 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(TeachersGroupDsts(values), TeachersMLDsts(teachers)) } else { actions = slicediff.Diff(TeachersGroupDsts(values), TeachersGroupSrcs(teachers)) } toBeAdded := make([]*orm.Teacher, 0) toBeRemoved := make([]string, 0) for _, a := range actions { switch a.Type { case slicediff.Remove: toBeRemoved = append(toBeRemoved, values[a.Id]) case slicediff.Add: teacher := teachers[a.Id] if !teacher.Exclude { toBeAdded = append(toBeAdded, teacher) } } } if !syncJob.conf.Sync.SafeRun { if len(toBeAdded) > 0 { log.Printf("ADDING %d teachers to %s group", len(toBeAdded), groupDN) } for _, teacher := range toBeAdded { log.Println(teacher.CompleteName()) err = ldapClient.AddTeacherToGroup(teacher, groupDN) if err != nil { return err } } } else { if len(toBeAdded) > 0 { log.Printf("%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 { log.Printf("REMOVING %d users from %s group...", len(toBeRemoved), groupDN) } for _, value := range toBeRemoved { log.Printf("REMOVE %s from %s group.", value, groupDN) ldapClient.RemoveTeacherFromGroupByMemberValue(value, groupDN) } } else { if len(toBeRemoved) > 0 { log.Printf("%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...") } log.Printf("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) } log.Printf("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.Teachers() if err != nil { log.Println(err) } log.Println("Retrieving Departments...") departments, err := karmenClient.GetDepartments() if err != nil { log.Println(err) } log.Println("Sync teachers...") if err := syncJob.syncTeachers(ldapClient, karmenClient, entries, teachers); err != nil { panic(err) } log.Println("Sync 'Docenti' group...") if err := syncJob.syncGroup(ldapClient, "cn=Docenti", teachers); err != nil { panic(err) } log.Println("Sync 'Tutti i docenti' ML...") if err := syncJob.syncGroup(ldapClient, "cn=Tutti i docenti,ou=Mailing Lists", teachers); err != nil { panic(err) } log.Println("Sync Departments and MLs...") for _, department := range departments { group := fmt.Sprintf("cn=%s,ou=Mailing Lists", department.Name) err = syncJob.syncGroup(ldapClient, group, department.Teachers) if err != nil { panic(err) } group = fmt.Sprintf("cn=%s,ou=Dipartimenti", department.Name) err = syncJob.syncGroup(ldapClient, group, department.Teachers) if err != nil { panic(err) } } }