sync.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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. var found bool
  107. for _, filter := range filters {
  108. if strings.HasPrefix(user.Username, filter) {
  109. found = true
  110. break
  111. }
  112. }
  113. // if there are filters but none of them match, then skip this user.
  114. if len(filters) > 0 && !found {
  115. continue
  116. }
  117. dbUser, ok := dbUsersMap[user.Username]
  118. if !ok {
  119. // create the user only if it doesn't exist.
  120. err = logic.CreateUser(&models.User{
  121. UserName: user.Username,
  122. ExternalIdentityProviderID: user.ID,
  123. DisplayName: user.DisplayName,
  124. AccountDisabled: user.AccountDisabled,
  125. Password: password,
  126. AuthType: models.OAuth,
  127. PlatformRoleID: models.ServiceUser,
  128. })
  129. if err != nil {
  130. return err
  131. }
  132. } else if dbUser.AuthType == models.OAuth {
  133. if dbUser.AccountDisabled != user.AccountDisabled ||
  134. dbUser.DisplayName != user.DisplayName ||
  135. dbUser.ExternalIdentityProviderID != user.ID {
  136. dbUser.AccountDisabled = user.AccountDisabled
  137. dbUser.DisplayName = user.DisplayName
  138. dbUser.ExternalIdentityProviderID = user.ID
  139. err = logic.UpsertUser(dbUser)
  140. if err != nil {
  141. return err
  142. }
  143. }
  144. } else {
  145. logger.Log(0, "user with username "+user.Username+" already exists, skipping creation")
  146. continue
  147. }
  148. }
  149. for _, user := range dbUsersMap {
  150. if user.ExternalIdentityProviderID == "" {
  151. continue
  152. }
  153. if _, ok := idpUsersMap[user.UserName]; !ok {
  154. // delete the user if it has been deleted on idp.
  155. err = logic.DeleteUser(user.UserName)
  156. if err != nil {
  157. return err
  158. }
  159. }
  160. }
  161. return nil
  162. }
  163. func syncGroups(idpGroups []idp.Group) error {
  164. dbGroups, err := proLogic.ListUserGroups()
  165. if err != nil && !database.IsEmptyRecord(err) {
  166. return err
  167. }
  168. dbUsers, err := logic.GetUsersDB()
  169. if err != nil && !database.IsEmptyRecord(err) {
  170. return err
  171. }
  172. idpGroupsMap := make(map[string]struct{})
  173. for _, group := range idpGroups {
  174. idpGroupsMap[group.ID] = struct{}{}
  175. }
  176. dbGroupsMap := make(map[string]models.UserGroup)
  177. for _, group := range dbGroups {
  178. if group.ExternalIdentityProviderID != "" {
  179. dbGroupsMap[group.ExternalIdentityProviderID] = group
  180. }
  181. }
  182. dbUsersMap := make(map[string]models.User)
  183. for _, user := range dbUsers {
  184. if user.ExternalIdentityProviderID != "" {
  185. dbUsersMap[user.ExternalIdentityProviderID] = user
  186. }
  187. }
  188. modifiedUsers := make(map[string]struct{})
  189. filters := logic.GetServerSettings().GroupFilters
  190. for _, group := range idpGroups {
  191. var found bool
  192. for _, filter := range filters {
  193. if strings.HasPrefix(group.Name, filter) {
  194. found = true
  195. break
  196. }
  197. }
  198. // if there are filters but none of them match, then skip this group.
  199. if len(filters) > 0 && !found {
  200. continue
  201. }
  202. dbGroup, ok := dbGroupsMap[group.ID]
  203. if !ok {
  204. dbGroup.ExternalIdentityProviderID = group.ID
  205. dbGroup.Name = group.Name
  206. dbGroup.Default = false
  207. dbGroup.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
  208. err := proLogic.CreateUserGroup(&dbGroup)
  209. if err != nil {
  210. return err
  211. }
  212. } else {
  213. dbGroup.Name = group.Name
  214. err = proLogic.UpdateUserGroup(dbGroup)
  215. if err != nil {
  216. return err
  217. }
  218. }
  219. groupMembersMap := make(map[string]struct{})
  220. for _, member := range group.Members {
  221. groupMembersMap[member] = struct{}{}
  222. }
  223. for _, user := range dbUsers {
  224. // use dbGroup.Name because the group name may have been changed on idp.
  225. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  226. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  227. if inNetmakerGroup && !inIDPGroup {
  228. // use dbGroup.Name because the group name may have been changed on idp.
  229. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  230. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  231. }
  232. if !inNetmakerGroup && inIDPGroup {
  233. // use dbGroup.Name because the group name may have been changed on idp.
  234. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  235. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  236. }
  237. }
  238. }
  239. for userID := range modifiedUsers {
  240. err = logic.UpsertUser(dbUsersMap[userID])
  241. if err != nil {
  242. return err
  243. }
  244. }
  245. for _, group := range dbGroups {
  246. if group.ExternalIdentityProviderID != "" {
  247. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  248. // delete the group if it has been deleted on idp.
  249. err = proLogic.DeleteUserGroup(group.ID)
  250. if err != nil {
  251. return err
  252. }
  253. }
  254. }
  255. }
  256. return nil
  257. }