sync.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. package sync
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "fmt"
  6. "log"
  7. "strings"
  8. "github.com/remogatto/slicediff"
  9. "github.com/sethvargo/go-password/password"
  10. gomail "gopkg.in/gomail.v2"
  11. ldap "gopkg.in/ldap.v2"
  12. "text/template"
  13. karmen_client "gogs.carduccidante.edu.it/karmen/core/client"
  14. "gogs.carduccidante.edu.it/karmen/core/config"
  15. karmen_ldap "gogs.carduccidante.edu.it/karmen/core/ldap"
  16. "gogs.carduccidante.edu.it/karmen/core/orm"
  17. tpl_util "gogs.carduccidante.edu.it/karmen/core/util/template"
  18. )
  19. type entriesToDN []*ldap.Entry
  20. func (entries entriesToDN) Convert() (result []string) {
  21. for _, entry := range entries {
  22. result = append(
  23. result,
  24. entry.DN,
  25. )
  26. }
  27. return
  28. }
  29. type usersToDNConverter []orm.User
  30. func (users usersToDNConverter) Convert() (result []string) {
  31. for _, user := range users {
  32. result = append(
  33. result,
  34. user.DN(),
  35. )
  36. }
  37. return
  38. }
  39. type usersToUsernamesConverter []orm.User
  40. func (users usersToUsernamesConverter) Convert() (result []string) {
  41. for _, user := range users {
  42. result = append(
  43. result,
  44. user.Username(),
  45. )
  46. }
  47. return
  48. }
  49. type SliceToStringSlicer []string
  50. func (slice SliceToStringSlicer) Convert() (result []string) {
  51. return slice
  52. }
  53. type Result struct {
  54. Description string
  55. Added []orm.User
  56. Updated []orm.User
  57. Removed []string
  58. }
  59. func (result *Result) String() string {
  60. output := fmt.Sprintf("\n%s\n", result.Description)
  61. if len(result.Added) > 0 {
  62. output += "\n== ADDED ==\n\n"
  63. for _, user := range result.Added {
  64. output += fmt.Sprintf("* %s\n", user.CompleteName())
  65. }
  66. }
  67. if len(result.Updated) > 0 {
  68. output += "\n== UPDATED ==\n\n"
  69. for _, user := range result.Updated {
  70. output += fmt.Sprintf("* %s\n", user.CompleteName())
  71. }
  72. }
  73. if len(result.Removed) > 0 {
  74. output += "\n== REMOVED ==\n\n"
  75. for _, user := range result.Removed {
  76. output += fmt.Sprintf("* %s\n", user)
  77. }
  78. }
  79. if len(result.Added)*len(result.Updated)*len(result.Removed) > 0 {
  80. output += "\n"
  81. }
  82. output += fmt.Sprintf("[%d added, %d updated, %d removed]", len(result.Added), len(result.Updated), len(result.Removed))
  83. return output
  84. }
  85. type SyncJob struct {
  86. conf *config.ConfigT
  87. }
  88. func NewSyncJob(conf *config.ConfigT) *SyncJob {
  89. return &SyncJob{conf}
  90. }
  91. func logf(fmt string, args ...interface{}) {
  92. if config.Config.Sync.Verbose {
  93. log.Printf(fmt, args...)
  94. }
  95. }
  96. func (syncJob *SyncJob) sendMail(user orm.User, tpl *template.Template) error {
  97. var buf bytes.Buffer
  98. err := tpl.Execute(&buf, user)
  99. if err != nil {
  100. return err
  101. }
  102. if !syncJob.conf.Sync.SafeRun {
  103. logf("SEND user credential to %s...", user.GetAltEmail())
  104. m := gomail.NewMessage()
  105. m.SetHeader("From", syncJob.conf.Smtp.From)
  106. m.SetHeader("To", user.GetAltEmail())
  107. m.SetHeader("Cc", syncJob.conf.Smtp.Cc)
  108. m.SetHeader("Subject", fmt.Sprintf("Attivazione dell'utenza %s per l'accesso ai servizi informatici del Liceo \"Carducci-Dante\" di Trieste", user.CompleteName()))
  109. m.SetBody("text/plain", buf.String())
  110. d := gomail.NewDialer(syncJob.conf.Smtp.Host, syncJob.conf.Smtp.Port, syncJob.conf.Smtp.Username, syncJob.conf.Smtp.Password)
  111. d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
  112. if err := d.DialAndSend(m); err != nil {
  113. return err
  114. }
  115. } else {
  116. logf("Credentials would be sent to %s, new password is %s", user.GetAltEmail(), user.GetPlainPassword())
  117. }
  118. return nil
  119. }
  120. func (syncJob *SyncJob) SyncUsers(ldapClient *karmen_ldap.Client, karmenClient *karmen_client.Client, entries []*ldap.Entry, users []orm.User) (*Result, error) {
  121. var mailTpl *template.Template
  122. actions := slicediff.Diff(entriesToDN(entries).Convert, usersToDNConverter(users).Convert)
  123. result := new(Result)
  124. result.Description = "Sync users..."
  125. for _, a := range actions {
  126. switch a.Type {
  127. case slicediff.Remove:
  128. entry := entries[a.Id]
  129. result.Removed = append(result.Removed, entry.DN)
  130. case slicediff.Add:
  131. user := users[a.Id]
  132. if !user.GetExclude() {
  133. result.Added = append(result.Added, user)
  134. }
  135. case slicediff.Update:
  136. user := users[a.Id]
  137. if !user.GetExclude() && user.GetRegenerate() {
  138. result.Updated = append(result.Updated, user)
  139. }
  140. if user.GetExclude() {
  141. result.Removed = append(result.Removed, user.DN())
  142. }
  143. }
  144. }
  145. if syncJob.conf.Sync.SendMail {
  146. var err error
  147. mailTpl, err = tpl_util.LoadTextTemplate("cron/sync/mail.tpl")
  148. if err != nil {
  149. return nil, err
  150. }
  151. }
  152. if !syncJob.conf.Sync.SafeRun {
  153. // Add
  154. for _, user := range result.Added {
  155. password, err := password.Generate(8, 2, 0, false, true)
  156. if err != nil {
  157. return nil, err
  158. }
  159. user.SetPlainPassword(password)
  160. err = ldapClient.AddUser(user)
  161. if err != nil {
  162. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  163. }
  164. if user.GetRegenerate() {
  165. if syncJob.conf.Sync.SendMail && user.GetAltEmail() != "" {
  166. err = syncJob.sendMail(user, mailTpl)
  167. if err != nil {
  168. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  169. }
  170. }
  171. user.SetRegenerate(false)
  172. err = karmenClient.UpdateUser(user)
  173. if err != nil {
  174. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  175. }
  176. }
  177. }
  178. // Remove
  179. for _, entry := range result.Removed {
  180. err := ldapClient.DeleteByDN(entry)
  181. if err != nil {
  182. return nil, fmt.Errorf("An error occurred for entry %s: %v", entry, err)
  183. }
  184. }
  185. // Update
  186. for _, user := range result.Updated {
  187. if user.GetRegenerate() {
  188. password, err := password.Generate(8, 2, 0, false, true)
  189. if err != nil {
  190. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  191. }
  192. user.SetPlainPassword(password)
  193. if syncJob.conf.Sync.SendMail && user.GetAltEmail() != "" {
  194. err = syncJob.sendMail(user, mailTpl)
  195. if err != nil {
  196. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  197. }
  198. }
  199. user.SetRegenerate(false)
  200. err = karmenClient.UpdateUser(user)
  201. if err != nil {
  202. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  203. }
  204. err = ldapClient.UpdateUserPassword(user)
  205. if err != nil {
  206. return nil, fmt.Errorf("An error occurred for user %s: %v", user.CompleteName(), err)
  207. }
  208. }
  209. }
  210. }
  211. return result, nil
  212. }
  213. func (syncJob *SyncJob) SyncGroup(ldapClient *karmen_ldap.Client, users []orm.User, groupDN string) (*Result, error) {
  214. var ml bool
  215. result := new(Result)
  216. result.Description = fmt.Sprintf("Sync group %s", groupDN)
  217. actions := make(map[string]*slicediff.Action)
  218. entries, err := ldapClient.GroupMembers(groupDN)
  219. if err != nil {
  220. return nil, err
  221. }
  222. values := make([]string, 0)
  223. if len(entries) > 0 {
  224. values = entries[0].Attributes[0].Values
  225. }
  226. if strings.Contains(groupDN, "Mailing Lists") {
  227. ml = true
  228. actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToDNConverter(users).Convert)
  229. } else {
  230. actions = slicediff.Diff(SliceToStringSlicer(values).Convert, usersToUsernamesConverter(users).Convert)
  231. }
  232. for _, a := range actions {
  233. switch a.Type {
  234. case slicediff.Remove:
  235. result.Removed = append(result.Removed, values[a.Id])
  236. case slicediff.Add:
  237. user := users[a.Id]
  238. if !user.GetExclude() {
  239. result.Added = append(result.Added, user)
  240. }
  241. case slicediff.Update:
  242. user := users[a.Id]
  243. if user.GetExclude() && !ml {
  244. result.Removed = append(result.Removed, user.Username())
  245. }
  246. }
  247. }
  248. if !syncJob.conf.Sync.SafeRun {
  249. for _, user := range result.Added {
  250. err = ldapClient.AddUserToGroup(user, groupDN)
  251. if err != nil {
  252. return nil, err
  253. }
  254. }
  255. for _, value := range result.Removed {
  256. ldapClient.RemoveUserFromGroupByMemberValue(value, groupDN)
  257. }
  258. }
  259. return result, nil
  260. }
  261. func (syncJob *SyncJob) Run() {
  262. if syncJob.conf.Sync.SafeRun {
  263. log.Println("Running in SAFE MODE...")
  264. }
  265. logf("Connecting to karmen at %s...", syncJob.conf.Url)
  266. karmenClient, err := karmen_client.Dial(syncJob.conf.Url, syncJob.conf.Admin.Username, syncJob.conf.Admin.Password)
  267. if err != nil {
  268. log.Println(err)
  269. }
  270. teachers, err := karmenClient.GetTeachers()
  271. if err != nil {
  272. log.Println(err)
  273. }
  274. logf("Connecting to LDAP at %s...", syncJob.conf.Ldap.Host)
  275. ldapClient, err := karmen_ldap.NewClient(syncJob.conf.Ldap.Host, syncJob.conf)
  276. if err != nil {
  277. log.Println(err)
  278. }
  279. log.Println("Retrieving teachers from LDAP...")
  280. entries, err := ldapClient.Users(syncJob.conf.Sync.TeachersSearchBase)
  281. if err != nil {
  282. log.Println(err)
  283. }
  284. log.Println("Retrieving Departments...")
  285. departments, err := karmenClient.GetDepartments()
  286. if err != nil {
  287. log.Println(err)
  288. }
  289. log.Println("Retrieving Offices...")
  290. offices, err := karmenClient.GetOffices()
  291. if err != nil {
  292. log.Println(err)
  293. }
  294. users := make([]orm.User, 0)
  295. for _, teacher := range teachers {
  296. users = append(users, teacher)
  297. }
  298. log.Println("Sync teachers...")
  299. if result, err := syncJob.SyncUsers(ldapClient, karmenClient, entries, users); err != nil {
  300. panic(err)
  301. } else {
  302. log.Println(result)
  303. }
  304. if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.TeachersGroup); err != nil {
  305. panic(err)
  306. } else {
  307. log.Println(result)
  308. }
  309. if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.TeachersML); err != nil {
  310. panic(err)
  311. } else {
  312. log.Println(result)
  313. }
  314. departmentsCoordinators := make([]orm.User, 0)
  315. for _, department := range departments {
  316. if department.Coordinator != nil {
  317. departmentsCoordinators = append(departmentsCoordinators, department.Coordinator)
  318. }
  319. users := make([]orm.User, 0)
  320. for _, teacher := range department.Teachers {
  321. users = append(users, teacher)
  322. }
  323. group := fmt.Sprintf("cn=%s,ou=Mailing Lists", department.Name)
  324. if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil {
  325. panic(err)
  326. } else {
  327. log.Println(result)
  328. }
  329. group = fmt.Sprintf("cn=%s,ou=Dipartimenti", department.Name)
  330. if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil {
  331. panic(err)
  332. } else {
  333. log.Println(result)
  334. }
  335. }
  336. // Class coordinators
  337. classCoordinators := make([]orm.User, 0)
  338. classes, err := karmenClient.GetClasses()
  339. if err != nil {
  340. panic(err)
  341. }
  342. for _, class := range classes {
  343. if class.Coordinator != nil {
  344. classCoordinators = append(classCoordinators, class.Coordinator)
  345. }
  346. }
  347. if result, err := syncJob.SyncGroup(ldapClient, classCoordinators, syncJob.conf.Sync.ClassCoordinatorsGroup); err != nil {
  348. panic(err)
  349. } else {
  350. log.Println(result)
  351. }
  352. if result, err := syncJob.SyncGroup(ldapClient, classCoordinators, syncJob.conf.Sync.ClassCoordinatorsML); err != nil {
  353. panic(err)
  354. } else {
  355. log.Println(result)
  356. }
  357. // Departments Coordinator
  358. log.Println("Sync Departments Coordinators...")
  359. for _, c := range departmentsCoordinators {
  360. log.Println("Coordinatore dipartimento", c.CompleteName())
  361. }
  362. if result, err := syncJob.SyncGroup(ldapClient, departmentsCoordinators, syncJob.conf.Sync.DepartmentsCoordinatorsGroup); err != nil {
  363. panic(err)
  364. } else {
  365. log.Println(result)
  366. }
  367. if result, err := syncJob.SyncGroup(ldapClient, departmentsCoordinators, syncJob.conf.Sync.DepartmentsCoordinatorsML); err != nil {
  368. panic(err)
  369. } else {
  370. log.Println(result)
  371. }
  372. administratives, err := karmenClient.GetAdministratives()
  373. if err != nil {
  374. log.Println(err)
  375. }
  376. log.Println("Getting administratives from LDAP...")
  377. entries, err = ldapClient.Users(syncJob.conf.Sync.AdministrativesSearchBase)
  378. if err != nil {
  379. log.Println(err)
  380. }
  381. users = make([]orm.User, 0)
  382. for _, administrative := range administratives {
  383. users = append(users, administrative)
  384. }
  385. log.Println("Sync administratives...")
  386. if result, err := syncJob.SyncUsers(ldapClient, karmenClient, entries, users); err != nil {
  387. panic(err)
  388. } else {
  389. log.Println(result)
  390. }
  391. if result, err := syncJob.SyncGroup(ldapClient, users, syncJob.conf.Sync.AdministrativesGroup); err != nil {
  392. panic(err)
  393. } else {
  394. log.Println(result)
  395. }
  396. for _, office := range offices {
  397. users := make([]orm.User, 0)
  398. for _, administrative := range office.Administratives {
  399. users = append(users, administrative)
  400. }
  401. group := fmt.Sprintf("cn=%s,ou=Segreterie", office.Name)
  402. if result, err := syncJob.SyncGroup(ldapClient, users, group); err != nil {
  403. panic(err)
  404. } else {
  405. log.Println(result)
  406. }
  407. }
  408. }