sync.go 7.5 KB

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