sync.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. package auth
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/gravitl/netmaker/database"
  9. "github.com/gravitl/netmaker/logger"
  10. "github.com/gravitl/netmaker/logic"
  11. "github.com/gravitl/netmaker/models"
  12. "github.com/gravitl/netmaker/pro/idp"
  13. "github.com/gravitl/netmaker/pro/idp/azure"
  14. "github.com/gravitl/netmaker/pro/idp/google"
  15. "github.com/gravitl/netmaker/pro/idp/okta"
  16. proLogic "github.com/gravitl/netmaker/pro/logic"
  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(settings.UserFilters)
  79. if err != nil {
  80. return err
  81. }
  82. idpGroups, err = idpClient.GetGroups(settings.GroupFilters)
  83. if err != nil {
  84. return err
  85. }
  86. if len(settings.GroupFilters) > 0 {
  87. idpUsers = filterUsersByGroupMembership(idpUsers, idpGroups)
  88. }
  89. if len(settings.UserFilters) > 0 {
  90. idpGroups = filterGroupsByMembers(idpGroups, idpUsers)
  91. }
  92. }
  93. err = syncUsers(idpUsers)
  94. if err != nil {
  95. return err
  96. }
  97. return syncGroups(idpGroups)
  98. }
  99. func syncUsers(idpUsers []idp.User) error {
  100. dbUsers, err := logic.GetUsersDB()
  101. if err != nil && !database.IsEmptyRecord(err) {
  102. return err
  103. }
  104. password, err := logic.FetchPassValue("")
  105. if err != nil {
  106. return err
  107. }
  108. idpUsersMap := make(map[string]struct{})
  109. for _, user := range idpUsers {
  110. idpUsersMap[user.Username] = struct{}{}
  111. }
  112. dbUsersMap := make(map[string]models.User)
  113. for _, user := range dbUsers {
  114. dbUsersMap[user.UserName] = user
  115. }
  116. filters := logic.GetServerSettings().UserFilters
  117. for _, user := range idpUsers {
  118. if user.AccountArchived {
  119. // delete the user if it has been archived.
  120. _ = logic.DeleteUser(user.Username)
  121. continue
  122. }
  123. var found bool
  124. for _, filter := range filters {
  125. if strings.HasPrefix(user.Username, filter) {
  126. found = true
  127. break
  128. }
  129. }
  130. // if there are filters but none of them match, then skip this user.
  131. if len(filters) > 0 && !found {
  132. continue
  133. }
  134. dbUser, ok := dbUsersMap[user.Username]
  135. if !ok {
  136. // create the user only if it doesn't exist.
  137. err = logic.CreateUser(&models.User{
  138. UserName: user.Username,
  139. ExternalIdentityProviderID: user.ID,
  140. DisplayName: user.DisplayName,
  141. AccountDisabled: user.AccountDisabled,
  142. Password: password,
  143. AuthType: models.OAuth,
  144. PlatformRoleID: models.ServiceUser,
  145. })
  146. if err != nil {
  147. return err
  148. }
  149. // It's possible that a user can attempt to log in to Netmaker
  150. // after the IDP is configured but before the users are synced.
  151. // Since the user doesn't exist, a pending user will be
  152. // created. Now, since the user is created, the pending user
  153. // can be deleted.
  154. _ = logic.DeletePendingUser(user.Username)
  155. } else if dbUser.AuthType == models.OAuth {
  156. if dbUser.AccountDisabled != user.AccountDisabled ||
  157. dbUser.DisplayName != user.DisplayName ||
  158. dbUser.ExternalIdentityProviderID != user.ID {
  159. dbUser.AccountDisabled = user.AccountDisabled
  160. dbUser.DisplayName = user.DisplayName
  161. dbUser.ExternalIdentityProviderID = user.ID
  162. err = logic.UpsertUser(dbUser)
  163. if err != nil {
  164. return err
  165. }
  166. }
  167. } else {
  168. logger.Log(0, "user with username "+user.Username+" already exists, skipping creation")
  169. continue
  170. }
  171. }
  172. for _, user := range dbUsersMap {
  173. if user.ExternalIdentityProviderID == "" {
  174. continue
  175. }
  176. if _, ok := idpUsersMap[user.UserName]; !ok {
  177. // delete the user if it has been deleted on idp.
  178. err = logic.DeleteUser(user.UserName)
  179. if err != nil {
  180. return err
  181. }
  182. }
  183. }
  184. return nil
  185. }
  186. func syncGroups(idpGroups []idp.Group) error {
  187. dbGroups, err := proLogic.ListUserGroups()
  188. if err != nil && !database.IsEmptyRecord(err) {
  189. return err
  190. }
  191. dbUsers, err := logic.GetUsersDB()
  192. if err != nil && !database.IsEmptyRecord(err) {
  193. return err
  194. }
  195. idpGroupsMap := make(map[string]struct{})
  196. for _, group := range idpGroups {
  197. idpGroupsMap[group.ID] = struct{}{}
  198. }
  199. dbGroupsMap := make(map[string]models.UserGroup)
  200. for _, group := range dbGroups {
  201. if group.ExternalIdentityProviderID != "" {
  202. dbGroupsMap[group.ExternalIdentityProviderID] = group
  203. }
  204. }
  205. dbUsersMap := make(map[string]models.User)
  206. for _, user := range dbUsers {
  207. if user.ExternalIdentityProviderID != "" {
  208. dbUsersMap[user.ExternalIdentityProviderID] = user
  209. }
  210. }
  211. modifiedUsers := make(map[string]struct{})
  212. filters := logic.GetServerSettings().GroupFilters
  213. for _, group := range idpGroups {
  214. var found bool
  215. for _, filter := range filters {
  216. if strings.HasPrefix(group.Name, filter) {
  217. found = true
  218. break
  219. }
  220. }
  221. // if there are filters but none of them match, then skip this group.
  222. if len(filters) > 0 && !found {
  223. continue
  224. }
  225. dbGroup, ok := dbGroupsMap[group.ID]
  226. if !ok {
  227. dbGroup.ExternalIdentityProviderID = group.ID
  228. dbGroup.Name = group.Name
  229. dbGroup.Default = false
  230. dbGroup.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
  231. err := proLogic.CreateUserGroup(&dbGroup)
  232. if err != nil {
  233. return err
  234. }
  235. } else {
  236. dbGroup.Name = group.Name
  237. err = proLogic.UpdateUserGroup(dbGroup)
  238. if err != nil {
  239. return err
  240. }
  241. }
  242. groupMembersMap := make(map[string]struct{})
  243. for _, member := range group.Members {
  244. groupMembersMap[member] = struct{}{}
  245. }
  246. for _, user := range dbUsers {
  247. // use dbGroup.Name because the group name may have been changed on idp.
  248. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  249. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  250. if inNetmakerGroup && !inIDPGroup {
  251. // use dbGroup.Name because the group name may have been changed on idp.
  252. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  253. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  254. }
  255. if !inNetmakerGroup && inIDPGroup {
  256. // use dbGroup.Name because the group name may have been changed on idp.
  257. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  258. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  259. }
  260. }
  261. }
  262. for userID := range modifiedUsers {
  263. err = logic.UpsertUser(dbUsersMap[userID])
  264. if err != nil {
  265. return err
  266. }
  267. }
  268. for _, group := range dbGroups {
  269. if group.ExternalIdentityProviderID != "" {
  270. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  271. // delete the group if it has been deleted on idp.
  272. err = proLogic.DeleteUserGroup(group.ID)
  273. if err != nil {
  274. return err
  275. }
  276. }
  277. }
  278. }
  279. return nil
  280. }
  281. func filterUsersByGroupMembership(idpUsers []idp.User, idpGroups []idp.Group) []idp.User {
  282. usersMap := make(map[string]int)
  283. for i, user := range idpUsers {
  284. usersMap[user.ID] = i
  285. }
  286. filteredUsersMap := make(map[string]int)
  287. for _, group := range idpGroups {
  288. for _, member := range group.Members {
  289. if userIdx, ok := usersMap[member]; ok {
  290. // user at index `userIdx` is a member of at least one of the
  291. // groups in the `idpGroups` list, so we keep it.
  292. filteredUsersMap[member] = userIdx
  293. }
  294. }
  295. }
  296. i := 0
  297. filteredUsers := make([]idp.User, len(filteredUsersMap))
  298. for _, userIdx := range filteredUsersMap {
  299. filteredUsers[i] = idpUsers[userIdx]
  300. i++
  301. }
  302. return filteredUsers
  303. }
  304. func filterGroupsByMembers(idpGroups []idp.Group, idpUsers []idp.User) []idp.Group {
  305. usersMap := make(map[string]int)
  306. for i, user := range idpUsers {
  307. usersMap[user.ID] = i
  308. }
  309. filteredGroupsMap := make(map[int]bool)
  310. for i, group := range idpGroups {
  311. var members []string
  312. for _, member := range group.Members {
  313. if _, ok := usersMap[member]; ok {
  314. members = append(members, member)
  315. }
  316. if len(members) > 0 {
  317. // the group at index `i` has members from the `idpUsers` list,
  318. // so we keep it.
  319. filteredGroupsMap[i] = true
  320. // filter out members that were not provided in the `idpUsers` list.
  321. idpGroups[i].Members = members
  322. }
  323. }
  324. }
  325. i := 0
  326. filteredGroups := make([]idp.Group, len(filteredGroupsMap))
  327. for groupIdx := range filteredGroupsMap {
  328. filteredGroups[i] = idpGroups[groupIdx]
  329. i++
  330. }
  331. return filteredGroups
  332. }