sync.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. package auth
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/gravitl/netmaker/database"
  6. "github.com/gravitl/netmaker/logger"
  7. "github.com/gravitl/netmaker/logic"
  8. "github.com/gravitl/netmaker/models"
  9. "github.com/gravitl/netmaker/pro/idp"
  10. "github.com/gravitl/netmaker/pro/idp/azure"
  11. "github.com/gravitl/netmaker/pro/idp/google"
  12. proLogic "github.com/gravitl/netmaker/pro/logic"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. var (
  18. cancelSyncHook context.CancelFunc
  19. hookStopWg sync.WaitGroup
  20. )
  21. func ResetIDPSyncHook() {
  22. if cancelSyncHook != nil {
  23. cancelSyncHook()
  24. hookStopWg.Wait()
  25. cancelSyncHook = nil
  26. }
  27. if logic.IsSyncEnabled() {
  28. ctx, cancel := context.WithCancel(context.Background())
  29. cancelSyncHook = cancel
  30. hookStopWg.Add(1)
  31. go runIDPSyncHook(ctx)
  32. }
  33. }
  34. func runIDPSyncHook(ctx context.Context) {
  35. defer hookStopWg.Done()
  36. ticker := time.NewTicker(logic.GetIDPSyncInterval())
  37. defer ticker.Stop()
  38. for {
  39. select {
  40. case <-ctx.Done():
  41. logger.Log(0, "idp sync hook stopped")
  42. return
  43. case <-ticker.C:
  44. if err := SyncFromIDP(); err != nil {
  45. logger.Log(0, "failed to sync from idp: ", err.Error())
  46. } else {
  47. logger.Log(0, "sync from idp complete")
  48. }
  49. }
  50. }
  51. }
  52. func SyncFromIDP() error {
  53. settings := logic.GetServerSettings()
  54. var idpClient idp.Client
  55. var idpUsers []idp.User
  56. var idpGroups []idp.Group
  57. var err error
  58. switch settings.AuthProvider {
  59. case "google":
  60. idpClient, err = google.NewGoogleWorkspaceClient()
  61. if err != nil {
  62. return err
  63. }
  64. case "azure-ad":
  65. idpClient = azure.NewAzureEntraIDClient()
  66. default:
  67. if settings.AuthProvider != "" {
  68. return fmt.Errorf("invalid auth provider: %s", settings.AuthProvider)
  69. }
  70. }
  71. if settings.AuthProvider != "" && idpClient != nil {
  72. idpUsers, err = idpClient.GetUsers()
  73. if err != nil {
  74. return err
  75. }
  76. idpGroups, err = idpClient.GetGroups()
  77. if err != nil {
  78. return err
  79. }
  80. }
  81. err = syncUsers(idpUsers)
  82. if err != nil {
  83. return err
  84. }
  85. return syncGroups(idpGroups)
  86. }
  87. func syncUsers(idpUsers []idp.User) error {
  88. dbUsers, err := logic.GetUsersDB()
  89. if err != nil && !database.IsEmptyRecord(err) {
  90. return err
  91. }
  92. password, err := logic.FetchPassValue("")
  93. if err != nil {
  94. return err
  95. }
  96. idpUsersMap := make(map[string]struct{})
  97. for _, user := range idpUsers {
  98. idpUsersMap[user.Username] = struct{}{}
  99. }
  100. dbUsersMap := make(map[string]models.User)
  101. for _, user := range dbUsers {
  102. dbUsersMap[user.UserName] = user
  103. }
  104. filters := logic.GetServerSettings().UserFilters
  105. for _, user := range idpUsers {
  106. if user.AccountArchived {
  107. // delete the user if it has been archived.
  108. _ = logic.DeleteUser(user.Username)
  109. continue
  110. }
  111. var found bool
  112. for _, filter := range filters {
  113. if strings.HasPrefix(user.Username, filter) {
  114. found = true
  115. break
  116. }
  117. }
  118. // if there are filters but none of them match, then skip this user.
  119. if len(filters) > 0 && !found {
  120. continue
  121. }
  122. dbUser, ok := dbUsersMap[user.Username]
  123. if !ok {
  124. // create the user only if it doesn't exist.
  125. err = logic.CreateUser(&models.User{
  126. UserName: user.Username,
  127. ExternalIdentityProviderID: user.ID,
  128. DisplayName: user.DisplayName,
  129. AccountDisabled: user.AccountDisabled,
  130. Password: password,
  131. AuthType: models.OAuth,
  132. PlatformRoleID: models.ServiceUser,
  133. })
  134. if err != nil {
  135. return err
  136. }
  137. // It's possible that a user can attempt to log in to Netmaker
  138. // after the IDP is configured but before the users are synced.
  139. // Since the user doesn't exist, a pending user will be
  140. // created. Now, since the user is created, the pending user
  141. // can be deleted.
  142. _ = logic.DeletePendingUser(user.Username)
  143. } else if dbUser.AuthType == models.OAuth {
  144. if dbUser.AccountDisabled != user.AccountDisabled ||
  145. dbUser.DisplayName != user.DisplayName ||
  146. dbUser.ExternalIdentityProviderID != user.ID {
  147. dbUser.AccountDisabled = user.AccountDisabled
  148. dbUser.DisplayName = user.DisplayName
  149. dbUser.ExternalIdentityProviderID = user.ID
  150. err = logic.UpsertUser(dbUser)
  151. if err != nil {
  152. return err
  153. }
  154. }
  155. } else {
  156. logger.Log(0, "user with username "+user.Username+" already exists, skipping creation")
  157. continue
  158. }
  159. }
  160. for _, user := range dbUsersMap {
  161. if user.ExternalIdentityProviderID == "" {
  162. continue
  163. }
  164. if _, ok := idpUsersMap[user.UserName]; !ok {
  165. // delete the user if it has been deleted on idp.
  166. err = logic.DeleteUser(user.UserName)
  167. if err != nil {
  168. return err
  169. }
  170. }
  171. }
  172. return nil
  173. }
  174. func syncGroups(idpGroups []idp.Group) error {
  175. dbGroups, err := proLogic.ListUserGroups()
  176. if err != nil && !database.IsEmptyRecord(err) {
  177. return err
  178. }
  179. dbUsers, err := logic.GetUsersDB()
  180. if err != nil && !database.IsEmptyRecord(err) {
  181. return err
  182. }
  183. idpGroupsMap := make(map[string]struct{})
  184. for _, group := range idpGroups {
  185. idpGroupsMap[group.ID] = struct{}{}
  186. }
  187. dbGroupsMap := make(map[string]models.UserGroup)
  188. for _, group := range dbGroups {
  189. if group.ExternalIdentityProviderID != "" {
  190. dbGroupsMap[group.ExternalIdentityProviderID] = group
  191. }
  192. }
  193. dbUsersMap := make(map[string]models.User)
  194. for _, user := range dbUsers {
  195. if user.ExternalIdentityProviderID != "" {
  196. dbUsersMap[user.ExternalIdentityProviderID] = user
  197. }
  198. }
  199. modifiedUsers := make(map[string]struct{})
  200. filters := logic.GetServerSettings().GroupFilters
  201. for _, group := range idpGroups {
  202. var found bool
  203. for _, filter := range filters {
  204. if strings.HasPrefix(group.Name, filter) {
  205. found = true
  206. break
  207. }
  208. }
  209. // if there are filters but none of them match, then skip this group.
  210. if len(filters) > 0 && !found {
  211. continue
  212. }
  213. dbGroup, ok := dbGroupsMap[group.ID]
  214. if !ok {
  215. dbGroup.ExternalIdentityProviderID = group.ID
  216. dbGroup.Name = group.Name
  217. dbGroup.Default = false
  218. dbGroup.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
  219. err := proLogic.CreateUserGroup(&dbGroup)
  220. if err != nil {
  221. return err
  222. }
  223. } else {
  224. dbGroup.Name = group.Name
  225. err = proLogic.UpdateUserGroup(dbGroup)
  226. if err != nil {
  227. return err
  228. }
  229. }
  230. groupMembersMap := make(map[string]struct{})
  231. for _, member := range group.Members {
  232. groupMembersMap[member] = struct{}{}
  233. }
  234. for _, user := range dbUsers {
  235. // use dbGroup.Name because the group name may have been changed on idp.
  236. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  237. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  238. if inNetmakerGroup && !inIDPGroup {
  239. // use dbGroup.Name because the group name may have been changed on idp.
  240. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  241. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  242. }
  243. if !inNetmakerGroup && inIDPGroup {
  244. // use dbGroup.Name because the group name may have been changed on idp.
  245. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  246. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  247. }
  248. }
  249. }
  250. for userID := range modifiedUsers {
  251. err = logic.UpsertUser(dbUsersMap[userID])
  252. if err != nil {
  253. return err
  254. }
  255. }
  256. for _, group := range dbGroups {
  257. if group.ExternalIdentityProviderID != "" {
  258. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  259. // delete the group if it has been deleted on idp.
  260. err = proLogic.DeleteUserGroup(group.ID)
  261. if err != nil {
  262. return err
  263. }
  264. }
  265. }
  266. }
  267. return nil
  268. }