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.carduccidante.edu.it/karmen/core/client" "gogs.carduccidante.edu.it/karmen/core/config" karmen_ldap "gogs.carduccidante.edu.it/karmen/core/ldap" "gogs.carduccidante.edu.it/karmen/core/orm" tpl_util "gogs.carduccidante.edu.it/karmen/core/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 Result struct { Description string Added []orm.User Updated []orm.User Removed []string } func (result *Result) String() string { output := fmt.Sprintf("\n%s\n", result.Description) if len(result.Added) > 0 { output += "\n== ADDED ==\n\n" for _, user := range result.Added { output += fmt.Sprintf("* %s\n", user.CompleteName()) } } if len(result.Updated) > 0 { output += "\n== UPDATED ==\n\n" for _, user := range result.Updated { output += fmt.Sprintf("* %s\n", user.CompleteName()) } } if len(result.Removed) > 0 { output += "\n== REMOVED ==\n\n" for _, user := range result.Removed { output += fmt.Sprintf("* %s\n", user) } } if len(result.Added)*len(result.Updated)*len(result.Removed) > 0 { output += "\n" } output += fmt.Sprintf("[%d added, %d updated, %d removed]", len(result.Added), len(result.Updated), len(result.Removed)) return output } 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) (*Result, error) { var mailTpl *template.Template actions := slicediff.Diff(entriesToDN(entries).Convert, usersToDNConverter(users).Convert) result := new(Result) result.Description = "Sync users..." for _, a := range actions { switch a.Type { case slicediff.Remove: entry := entries[a.Id] result.Removed = append(result.Removed, entry.DN) case slicediff.Add: user := users[a.Id] if !user.GetExclude() { result.Added = append(result.Added, user) } case slicediff.Update: user := users[a.Id] if !user.GetExclude() && user.GetRegenerate() { result.Updated = append(result.Updated, user) } if user.GetExclude() { result.Removed = append(result.Removed, user.DN()) } } } if syncJob.conf.Sync.SendMail { var err error mailTpl, err = tpl_util.LoadTextTemplate("cron/sync/mail.tpl") if err != nil { return nil, err } } if !syncJob.conf.Sync.SafeRun { // Add for _, user := range result.Added { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return nil, err } user.SetPlainPassword(password) err = ldapClient.AddUser(user) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } if user.GetRegenerate() { if syncJob.conf.Sync.SendMail && user.GetAltEmail() != "" { err = syncJob.sendMail(user, mailTpl) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } } user.SetRegenerate(false) err = karmenClient.UpdateUser(user) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } } } // Remove for _, entry := range result.Removed { err := ldapClient.DeleteByDN(entry) if err != nil { return nil, fmt.Errorf("An error occurred for entry %s: %v", entry, err) } } // Update for _, user := range result.Updated { if user.GetRegenerate() { password, err := password.Generate(8, 2, 0, false, true) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } user.SetPlainPassword(password) if syncJob.conf.Sync.SendMail && user.GetAltEmail() != "" { err = syncJob.sendMail(user, mailTpl) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } } user.SetRegenerate(false) err = karmenClient.UpdateUser(user) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } err = ldapClient.UpdateUserPassword(user) if err != nil { return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err) } } } } return result, nil } func (syncJob *SyncJob) SyncGroup(ldapClient *karmen_ldap.Client, users []orm.User, groupDN string) (*Result, error) { var ml bool result := new(Result) result.Description = fmt.Sprintf("Sync group %s", groupDN) actions := make(map[string]*slicediff.Action) entries, err := ldapClient.GroupMembers(groupDN) if err != nil { return nil, err } values := make([]string, 0) if len(entries) > 0 { values = entries[0].Attributes[0].Values } if strings.Contains(groupDN, "Mailing Lists") { ml = true actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToDNConverter(users).Convert) } else { actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToUsernamesConverter(users).Convert) } for _, a := range actions { switch a.Type { case slicediff.Remove: result.Removed = append(result.Removed, values[a.Id]) case slicediff.Add: user := users[a.Id] if !user.GetExclude() { result.Added = append(result.Added, user) } case slicediff.Update: user := users[a.Id] if user.GetExclude() && !ml { result.Removed = append(result.Removed, user.Username()) } } } if !syncJob.conf.Sync.SafeRun { for _, user := range result.Added { err = ldapClient.AddUserToGroup(user, groupDN) if err != nil { return nil, err } } for _, value := range result.Removed { ldapClient.RemoveUserFromGroupByMemberValue(value, groupDN) } } return result, 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(syncJob.conf.Sync.TeachersSearchBase) if err != nil { log.Println(err) } log.Println("Retrieving Departments...") departments, err := karmenClient.GetDepartments() if err != nil { log.Println(err) } log.Println("Retrieving Offices...") offices, err := karmenClient.GetOffices() if err != nil { log.Println(err) } users := make([]orm.User, 0) for _, teacher := range teachers { users = append(users, teacher) } log.Println("Sync teachers...") if result, err := syncJob.SyncUsers(ldapClient, karmenClient, entries, users); err != nil { panic(err) } else { log.Println(result) } if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.TeachersGroup); err != nil { panic(err) } else { log.Println(result) } if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.TeachersML); err != nil { panic(err) } else { log.Println(result) } departmentsCoordinators := make([]orm.User, 0) for _, department := range departments { if department.Coordinator != nil { departmentsCoordinators = append(departmentsCoordinators, department.Coordinator) } users := make([]orm.User, 0) for _, teacher := range department.Teachers { users = append(users, teacher) } group := fmt.Sprintf("cn=%s,ou=Mailing Lists", department.Name) if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil { panic(err) } else { log.Println(result) } group = fmt.Sprintf("cn=%s,ou=Dipartimenti", department.Name) if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil { panic(err) } else { log.Println(result) } } // Class coordinators classCoordinators := make([]orm.User, 0) classes, err := karmenClient.GetClasses() if err != nil { panic(err) } for _, class := range classes { if class.Coordinator != nil { classCoordinators = append(classCoordinators, class.Coordinator) } } if result, err := syncJob.SyncGroup(ldapClient, classCoordinators, syncJob.conf.Sync.ClassCoordinatorsGroup); err != nil { panic(err) } else { log.Println(result) } if result, err := syncJob.SyncGroup(ldapClient, classCoordinators, syncJob.conf.Sync.ClassCoordinatorsML); err != nil { panic(err) } else { log.Println(result) } // Departments Coordinator log.Println("Sync Departments Coordinators...") for _, c := range departmentsCoordinators { log.Println("Coordinatore dipartimento", c.CompleteName()) } if result, err := syncJob.SyncGroup(ldapClient, departmentsCoordinators, syncJob.conf.Sync.DepartmentsCoordinatorsGroup); err != nil { panic(err) } else { log.Println(result) } if result, err := syncJob.SyncGroup(ldapClient, departmentsCoordinators, syncJob.conf.Sync.DepartmentsCoordinatorsML); err != nil { panic(err) } else { log.Println(result) } administratives, err := karmenClient.GetAdministratives() if err != nil { log.Println(err) } log.Println("Getting administratives from LDAP...") entries, err = ldapClient.Users(syncJob.conf.Sync.AdministrativesSearchBase) if err != nil { log.Println(err) } users = make([]orm.User, 0) for _, administrative := range administratives { users = append(users, administrative) } log.Println("Sync administratives...") if result, err := syncJob.SyncUsers(ldapClient, karmenClient, entries, users); err != nil { panic(err) } else { log.Println(result) } if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.AdministrativesGroup); err != nil { panic(err) } else { log.Println(result) } for _, office := range offices { users := make([]orm.User, 0) for _, administrative := range office.Administratives { users = append(users, administrative) } group := fmt.Sprintf("cn=%s,ou=Segreterie", office.Name) if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil { panic(err) } else { log.Println(result) } } }