sync.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package ldap
  15. import (
  16. "context"
  17. "database/sql"
  18. "github.com/go-ldap/ldap/v3"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. api "yunion.io/x/onecloud/pkg/apis/identity"
  23. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  24. "yunion.io/x/onecloud/pkg/keystone/models"
  25. "yunion.io/x/onecloud/pkg/keystone/options"
  26. "yunion.io/x/onecloud/pkg/util/ldaputils"
  27. )
  28. func (drv *SLDAPDriver) Probe(ctx context.Context) error {
  29. cli, err := drv.getClient()
  30. if err != nil {
  31. return errors.Wrap(err, "getClient")
  32. }
  33. defer cli.Close()
  34. return nil
  35. }
  36. func (drv *SLDAPDriver) Sync(ctx context.Context) error {
  37. cli, err := drv.getClient()
  38. if err != nil {
  39. return errors.Wrap(err, "getClient")
  40. }
  41. defer cli.Close()
  42. if len(drv.ldapConfig.DomainTreeDN) > 0 {
  43. return drv.syncDomains(ctx, cli)
  44. } else {
  45. return drv.syncSingleDomain(ctx, cli)
  46. }
  47. }
  48. func (drv *SLDAPDriver) syncSingleDomain(ctx context.Context, cli *ldaputils.SLDAPClient) error {
  49. var domain *models.SDomain
  50. if len(drv.TargetDomainId) > 0 {
  51. targetDomain, err := models.DomainManager.FetchDomainById(drv.TargetDomainId)
  52. if err != nil && err != sql.ErrNoRows {
  53. return errors.Wrap(err, "models.DomainManager.FetchDomainById")
  54. }
  55. if targetDomain == nil {
  56. log.Warningln("target domain not exist!")
  57. } else {
  58. domain = targetDomain
  59. }
  60. }
  61. if domain == nil {
  62. domainInfo := SDomainInfo{DN: drv.ldapConfig.Suffix, Id: api.DefaultRemoteDomainId, Name: drv.IdpName}
  63. newDomain, err := drv.syncDomainInfo(ctx, domainInfo)
  64. if err != nil {
  65. return errors.Wrap(err, "syncDomainInfo")
  66. }
  67. domain = newDomain
  68. }
  69. userIdMap, err := drv.syncUsers(ctx, cli, domain.Id, drv.getUserTreeDN())
  70. if err != nil {
  71. return errors.Wrap(err, "syncUsers")
  72. }
  73. err = drv.syncGroups(ctx, cli, domain.Id, drv.getGroupTreeDN(), userIdMap)
  74. if err != nil {
  75. return errors.Wrap(err, "syncGroups")
  76. }
  77. return nil
  78. }
  79. func (drv *SLDAPDriver) searchDomainEntries(cli *ldaputils.SLDAPClient, domainid string, entryFunc func(*ldap.Entry) error) error {
  80. attrMap := make(map[string]string)
  81. if len(domainid) > 0 {
  82. attrMap[drv.ldapConfig.DomainIdAttribute] = domainid
  83. }
  84. _, err := cli.Search(drv.getDomainTreeDN(),
  85. drv.ldapConfig.DomainObjectclass,
  86. attrMap,
  87. drv.ldapConfig.DomainFilter,
  88. drv.domainAttributeList(),
  89. drv.domainQueryScope(),
  90. options.Options.LdapSearchPageSize, 0,
  91. func(offset uint32, entry *ldap.Entry) error {
  92. return entryFunc(entry)
  93. },
  94. )
  95. if err != nil {
  96. return errors.Wrap(err, "Search")
  97. }
  98. return nil
  99. }
  100. func (drv *SLDAPDriver) syncDomains(ctx context.Context, cli *ldaputils.SLDAPClient) error {
  101. domainIds := make([]string, 0)
  102. err := drv.searchDomainEntries(cli, "", func(entry *ldap.Entry) error {
  103. domainInfo := drv.entry2Domain(entry)
  104. err := domainInfo.isValid()
  105. if err != nil {
  106. log.Errorf("invalid domainInfo: %s, skip", err)
  107. return nil
  108. }
  109. domain, err := drv.syncDomainInfo(ctx, domainInfo)
  110. if err != nil {
  111. return errors.Wrap(err, "syncDomainInfo")
  112. }
  113. domainIds = append(domainIds, domain.Id)
  114. userIdMap, err := drv.syncUsers(ctx, cli, domain.Id, domainInfo.DN)
  115. if err != nil {
  116. return errors.Wrap(err, "syncUsers")
  117. }
  118. err = drv.syncGroups(ctx, cli, domain.Id, domainInfo.DN, userIdMap)
  119. if err != nil {
  120. return errors.Wrap(err, "syncGroups")
  121. }
  122. return nil
  123. })
  124. if err != nil {
  125. return errors.Wrap(err, "searchDomainEntries")
  126. }
  127. // remove any obsolete domains
  128. obsoleteDomainIds, err := models.IdmappingManager.FetchPublicIdsExcludes(drv.IdpId, api.IdMappingEntityDomain, domainIds)
  129. if err != nil {
  130. return errors.Wrap(err, "models.IdmappingManager.FetchPublicIdsExcludes")
  131. }
  132. for _, obsoleteDomainId := range obsoleteDomainIds {
  133. obsoleteDomain, err := models.DomainManager.FetchDomainById(obsoleteDomainId)
  134. if err != nil {
  135. log.Errorf("models.DomainManager.FetchDomainById error %s", err)
  136. continue
  137. }
  138. obsoleteDomain.AppendDescription(models.GetDefaultAdminCred(), "domain source removed")
  139. // unlink with Idp
  140. err = obsoleteDomain.UnlinkIdp(drv.IdpId)
  141. if err != nil {
  142. log.Errorf("obsoleteDomain.UnlinkIdp error %s", err)
  143. continue
  144. }
  145. // remove any user and groups
  146. err = obsoleteDomain.DeleteUserGroups(ctx, models.GetDefaultAdminCred())
  147. if err != nil {
  148. log.Errorf("domain.DeleteUserGroups error %s", err)
  149. continue
  150. }
  151. err = obsoleteDomain.ValidateDeleteCondition(ctx, nil)
  152. if err != nil {
  153. log.Errorf("obsoleteDomain.ValidateDeleteCondition error %s", err)
  154. continue
  155. }
  156. err = obsoleteDomain.Delete(ctx, models.GetDefaultAdminCred())
  157. if err != nil {
  158. log.Errorf("obsoleteDomain.Delete error %s", err)
  159. continue
  160. }
  161. }
  162. return nil
  163. }
  164. func (drv *SLDAPDriver) syncDomainInfo(ctx context.Context, info SDomainInfo) (*models.SDomain, error) {
  165. idp, err := models.IdentityProviderManager.FetchIdentityProviderById(drv.IdpId)
  166. if err != nil {
  167. return nil, errors.Wrap(err, "drv.GetIdentityProvider")
  168. }
  169. return idp.SyncOrCreateDomain(ctx, info.Id, info.Name, info.DN, true)
  170. }
  171. func (drv *SLDAPDriver) syncUsers(ctx context.Context, cli *ldaputils.SLDAPClient, domainId string, baseDN string) (map[string]string, error) {
  172. userIds := make([]string, 0)
  173. userIdMap := make(map[string]string)
  174. _, err := cli.Search(baseDN,
  175. drv.ldapConfig.UserObjectclass,
  176. nil,
  177. drv.ldapConfig.UserFilter,
  178. drv.userAttributeList(),
  179. drv.userQueryScope(),
  180. options.Options.LdapSearchPageSize, 0,
  181. func(offset uint32, entry *ldap.Entry) error {
  182. userInfo := drv.entry2User(entry)
  183. err := userInfo.isValid()
  184. if err != nil {
  185. log.Debugf("userInfo is invalid: %s, skip", err)
  186. return nil
  187. }
  188. userId, err := drv.syncUserDB(ctx, userInfo, domainId)
  189. if err != nil {
  190. return errors.Wrap(err, "syncUserDB")
  191. }
  192. userIds = append(userIds, userId)
  193. if drv.ldapConfig.GroupMembersAreIds {
  194. userIdMap[userInfo.Id] = userId
  195. } else {
  196. userIdMap[userInfo.DN] = userId
  197. }
  198. return nil
  199. },
  200. )
  201. if err != nil {
  202. return nil, errors.Wrap(err, "searchLDAP")
  203. }
  204. deleteUsers, err := models.UserManager.FetchUsersInDomain(domainId, userIds)
  205. if err != nil {
  206. return nil, errors.Wrap(err, "models.UserManager.FetchUserIdsInDomain")
  207. }
  208. for i := range deleteUsers {
  209. if !deleteUsers[i].LinkedWithIdp(drv.IdpId) {
  210. continue
  211. }
  212. err := deleteUsers[i].UnlinkIdp(drv.IdpId)
  213. if err != nil {
  214. log.Errorf("deleteUser.UnlinkIdp error %s", err)
  215. continue
  216. }
  217. err = deleteUsers[i].ValidateDeleteCondition(ctx, nil)
  218. if err != nil {
  219. log.Errorf("deleteUser.ValidateDeleteCondition error %s", err)
  220. continue
  221. }
  222. err = deleteUsers[i].Delete(ctx, models.GetDefaultAdminCred())
  223. if err != nil {
  224. log.Errorf("deleteUser.Delete error %s", err)
  225. continue
  226. }
  227. }
  228. return userIdMap, nil
  229. }
  230. func (drv *SLDAPDriver) syncUserDB(ctx context.Context, ui SUserInfo, domainId string) (string, error) {
  231. idp, err := models.IdentityProviderManager.FetchIdentityProviderById(drv.IdpId)
  232. if err != nil {
  233. return "", errors.Wrap(err, "models.IdentityProviderManager.FetchIdentityProviderById")
  234. }
  235. enableDefault := !drv.ldapConfig.DisableUserOnImport
  236. if !ui.Enabled {
  237. enableDefault = false
  238. }
  239. usr, err := idp.SyncOrCreateUser(ctx, ui.Id, ui.Name, domainId, enableDefault, func(user *models.SUser) {
  240. // LDAP user is always enabled
  241. // if ui.Enabled {
  242. // user.Enabled = tristate.True
  243. // } else {
  244. // user.Enabled = tristate.False
  245. // }
  246. if val, ok := ui.Extra["email"]; ok && len(val) > 0 && len(user.Email) == 0 {
  247. user.Email = val
  248. }
  249. if val, ok := ui.Extra["displayname"]; ok && len(val) > 0 && len(user.Displayname) == 0 {
  250. user.Displayname = val
  251. }
  252. if val, ok := ui.Extra["mobile"]; ok && len(val) > 0 && len(user.Mobile) == 0 {
  253. user.Mobile = val
  254. }
  255. tags := make(map[string]interface{})
  256. for k, v := range ui.Extra {
  257. tags[db.CLOUD_TAG_PREFIX+k] = v
  258. }
  259. err := user.SetAllMetadata(ctx, tags, models.GetDefaultAdminCred())
  260. if err != nil {
  261. log.Errorf("SetCloudMetadataAll %s fail %s", jsonutils.Marshal(tags), err)
  262. }
  263. })
  264. if err != nil {
  265. return "", errors.Wrap(err, "idp.SyncOrCreateUser")
  266. }
  267. return usr.Id, nil
  268. }
  269. func (drv *SLDAPDriver) syncGroups(ctx context.Context, cli *ldaputils.SLDAPClient, domainId string, baseDN string, userIdMap map[string]string) error {
  270. groupIds := make([]string, 0)
  271. _, err := cli.Search(baseDN,
  272. drv.ldapConfig.GroupObjectclass,
  273. nil,
  274. drv.ldapConfig.GroupFilter,
  275. drv.groupAttributeList(),
  276. drv.groupQueryScope(),
  277. options.Options.LdapSearchPageSize, 0,
  278. func(offset uint32, entry *ldap.Entry) error {
  279. groupInfo := drv.entry2Group(entry)
  280. err := groupInfo.isValid()
  281. if err != nil {
  282. log.Errorf("invalid group info: %s, skip", err)
  283. return nil
  284. }
  285. groupId, err := drv.syncGroupDB(ctx, groupInfo, domainId, userIdMap)
  286. if err != nil {
  287. return errors.Wrap(err, "syncGroupDB")
  288. }
  289. groupIds = append(groupIds, groupId)
  290. return nil
  291. },
  292. )
  293. if err != nil {
  294. return errors.Wrap(err, "searchLDAP")
  295. }
  296. deleteGroups, err := models.GroupManager.FetchGroupsInDomain(domainId, groupIds)
  297. if err != nil {
  298. return errors.Wrap(err, "models.GroupManager.FetchGroupsInDomain")
  299. }
  300. for i := range deleteGroups {
  301. if !deleteGroups[i].LinkedWithIdp(drv.IdpId) {
  302. continue
  303. }
  304. err := deleteGroups[i].UnlinkIdp(drv.IdpId)
  305. if err != nil {
  306. log.Errorf("deleteGroup.UnlinkIdp error %s", err)
  307. continue
  308. }
  309. err = deleteGroups[i].ValidateDeleteCondition(ctx, nil)
  310. if err != nil {
  311. log.Errorf("deleteGroup.ValidateDeleteCondition error %s", err)
  312. continue
  313. }
  314. err = deleteGroups[i].Delete(ctx, models.GetDefaultAdminCred())
  315. if err != nil {
  316. log.Errorf("deleteGroup.Delete error %s", err)
  317. continue
  318. }
  319. }
  320. return nil
  321. }
  322. func (drv *SLDAPDriver) syncGroupDB(ctx context.Context, groupInfo SGroupInfo, domainId string, userIdMap map[string]string) (string, error) {
  323. grp, err := models.GroupManager.RegisterExternalGroup(ctx, drv.IdpId, domainId, groupInfo.Id, groupInfo.Name)
  324. if err != nil {
  325. return "", errors.Wrap(err, "GroupManager.RegisterExternalGroup")
  326. }
  327. userIds := make([]string, 0)
  328. for _, userExtId := range groupInfo.Members {
  329. if uid, ok := userIdMap[userExtId]; ok {
  330. userIds = append(userIds, uid)
  331. }
  332. }
  333. models.UsergroupManager.SyncGroupUsers(ctx, models.GetDefaultAdminCred(), grp.Id, userIds)
  334. return grp.Id, nil
  335. }