sync.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 bingoiam
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "net/http"
  20. "net/url"
  21. "strconv"
  22. "time"
  23. json "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/tristate"
  27. "yunion.io/x/pkg/util/httputils"
  28. "yunion.io/x/onecloud/pkg/keystone/models"
  29. )
  30. type SBase struct {
  31. Id string `json:"id"`
  32. Name string `json:"name"`
  33. Code string `json:"code"`
  34. Email string `json:"email"`
  35. Mobile string `json:"mobile"`
  36. ParentId string `json:"parentId"`
  37. ExternalId string `json:"externalId"`
  38. Active bool `json:"active"`
  39. IsDeleted bool `json:"isDeleted"`
  40. CreatedAt time.Time `json:"createdAt"`
  41. CreatedBy string `json:"createdBy"`
  42. UpdatedAt time.Time `json:"updatedAt"`
  43. UpdatedBy string `json:"updatedBy"`
  44. }
  45. type STenant struct {
  46. SBase
  47. }
  48. type SOrganization struct {
  49. SBase
  50. TenantId string `json:"tenantId"`
  51. }
  52. type SUser struct {
  53. SBase
  54. OrgId string `json:"orgId"`
  55. UserName string `json:"userName"`
  56. TenantId string `json:"tenantId"`
  57. }
  58. type SApp struct {
  59. SBase
  60. Name string `json:"title"`
  61. TenantId string `json:"tenantId"`
  62. AppType string `json:"appType"`
  63. ProjectId string `json:"projectId"`
  64. Description string `json:"description"`
  65. }
  66. type SProject struct {
  67. Id string `json:"id"`
  68. Name string `json:"name"`
  69. TenantId string `json:"tenantId"`
  70. }
  71. func (drv *SBingoIAMOAuth2Driver) syncTenants(ctx context.Context, idp *models.SIdentityProvider) error {
  72. var count, page = 0, 0
  73. for {
  74. page++
  75. total, tenants, err := drv.getTenants(ctx, page, 500)
  76. if err != nil {
  77. return err
  78. }
  79. for _, tenant := range tenants {
  80. domain, err := idp.SyncOrCreateDomain(ctx, tenant.Id, tenant.Name, fmt.Sprintf("Sync from %s", idp.Name), false)
  81. if err != nil {
  82. return errors.Wrap(err, "idp.SyncOrCreateDomain")
  83. }
  84. drv.domains[tenant.Id] = domain
  85. }
  86. count += len(tenants)
  87. if count >= total {
  88. break
  89. }
  90. time.Sleep(time.Millisecond * 200)
  91. }
  92. return nil
  93. }
  94. func (drv *SBingoIAMOAuth2Driver) syncOrganizations(ctx context.Context, idp *models.SIdentityProvider) error {
  95. var loader func(filters string, orgs chan []*SOrganization)
  96. loader = func(filters string, orgs chan []*SOrganization) {
  97. var page, count = 0, 0
  98. var childOrgs []*SOrganization
  99. for {
  100. page++
  101. total, orgs, err := drv.getOrganizations(ctx, filters, page, 500)
  102. if err != nil {
  103. log.Errorf("get organizations %s fail %s", filters, err)
  104. return
  105. }
  106. childOrgs = append(childOrgs, orgs...)
  107. count += len(orgs)
  108. if count >= total {
  109. break
  110. }
  111. time.Sleep(time.Millisecond * 200)
  112. }
  113. if len(childOrgs) > 0 {
  114. orgs <- childOrgs
  115. }
  116. for _, org := range childOrgs {
  117. loader("parentId eq "+org.Id, orgs)
  118. }
  119. }
  120. var allOrgs = make(chan []*SOrganization)
  121. go func() {
  122. for orgs := range allOrgs {
  123. for _, org := range orgs {
  124. domain := drv.domains[org.TenantId]
  125. if domain == nil {
  126. continue
  127. }
  128. //TODO save to db
  129. }
  130. }
  131. }()
  132. loader("parentId is null", allOrgs)
  133. close(allOrgs)
  134. return nil
  135. }
  136. func (drv *SBingoIAMOAuth2Driver) syncProjects(ctx context.Context, idp *models.SIdentityProvider) error {
  137. var count, page = 0, 0
  138. for {
  139. page++
  140. total, projects, err := drv.getProjects(ctx, page, 500)
  141. if err != nil {
  142. return err
  143. }
  144. for _, project := range projects {
  145. domain := drv.domains[project.TenantId]
  146. if domain == nil {
  147. continue
  148. }
  149. ret, err := models.ProjectManager.FetchProject("", project.Name, domain.Id, "")
  150. if err != nil {
  151. log.Errorf("fetch project %s fail %s", project.Name, err)
  152. if errors.Cause(err) == sql.ErrNoRows {
  153. ret, err = models.ProjectManager.NewProject(ctx, project.Name, fmt.Sprintf("Sync from %s", idp.Name), domain.Id)
  154. if err != nil {
  155. continue
  156. }
  157. }
  158. }
  159. drv.projects[project.Id] = ret
  160. }
  161. count += len(projects)
  162. if count >= total {
  163. break
  164. }
  165. time.Sleep(time.Millisecond * 200)
  166. }
  167. return nil
  168. }
  169. func (drv *SBingoIAMOAuth2Driver) syncUsers(ctx context.Context, idp *models.SIdentityProvider) error {
  170. var count, page = 0, 0
  171. for {
  172. page++
  173. total, users, err := drv.getUsers(ctx, page, 500)
  174. if err != nil {
  175. return err
  176. }
  177. for _, user := range users {
  178. domain := drv.domains[user.TenantId]
  179. if domain == nil {
  180. continue
  181. }
  182. _, err = idp.SyncOrCreateUser(ctx, user.Id, user.UserName, domain.Id, true, func(u *models.SUser) {
  183. u.Id = user.Id
  184. u.Name = user.UserName
  185. u.Displayname = user.Name
  186. u.Description = fmt.Sprintf("Sync from %s", idp.Name)
  187. u.Email = user.Email
  188. u.DomainId = domain.Id
  189. u.Mobile = user.Mobile
  190. u.Enabled = tristate.True
  191. u.SEnabledIdentityBaseResource.Enabled = tristate.True
  192. })
  193. if err != nil {
  194. return errors.Wrap(err, "idp.SyncOrCreateDomain")
  195. }
  196. }
  197. count += len(users)
  198. if count >= total {
  199. break
  200. }
  201. time.Sleep(time.Millisecond * 200)
  202. }
  203. return nil
  204. }
  205. func (drv *SBingoIAMOAuth2Driver) getTenants(ctx context.Context, page, pageSize int) (int, []*STenant, error) {
  206. var accessToken = drv.accessToken
  207. if accessToken == "" {
  208. accessToken, _ = drv.getAccessToken(ctx)
  209. }
  210. urlStrStr := fmt.Sprintf("%v/tenant?total=true&page_size=%v&page=%v", drv.getIAMApiEndpoint(ctx), pageSize, page)
  211. headers := http.Header{}
  212. headers.Set("Authorization", "Bearer "+accessToken)
  213. return doRequest[[]*STenant](ctx, urlStrStr, httputils.GET, headers, nil)
  214. }
  215. func (drv *SBingoIAMOAuth2Driver) getOrganizations(ctx context.Context, filters string, page, pageSize int) (int, []*SOrganization, error) {
  216. var accessToken = drv.accessToken
  217. if accessToken == "" {
  218. accessToken, _ = drv.getAccessToken(ctx)
  219. }
  220. urlStr := fmt.Sprintf("%v/organization?total=true&page_size=%v&page=%v&filters=%v", drv.getIAMApiEndpoint(ctx), pageSize, page, url.QueryEscape(filters))
  221. headers := http.Header{}
  222. headers.Set("Authorization", "Bearer "+accessToken)
  223. return doRequest[[]*SOrganization](ctx, urlStr, httputils.GET, headers, nil)
  224. }
  225. func (drv *SBingoIAMOAuth2Driver) getUsers(ctx context.Context, page, pageSize int) (int, []*SUser, error) {
  226. var accessToken = drv.accessToken
  227. if accessToken == "" {
  228. accessToken, _ = drv.getAccessToken(ctx)
  229. }
  230. urlStr := fmt.Sprintf("%v/user?total=true&page_size=%v&page=%v", drv.getIAMApiEndpoint(ctx), pageSize, page)
  231. headers := http.Header{}
  232. headers.Set("Authorization", "Bearer "+accessToken)
  233. return doRequest[[]*SUser](ctx, urlStr, httputils.GET, headers, nil)
  234. }
  235. func (drv *SBingoIAMOAuth2Driver) getProjects(ctx context.Context, page, pageSize int) (int, []*SProject, error) {
  236. var accessToken = drv.accessToken
  237. if accessToken == "" {
  238. accessToken, _ = drv.getAccessToken(ctx)
  239. }
  240. urlStr := fmt.Sprintf("%v/project?total=true&page_size=%v&page=%v", drv.getIAMApiEndpoint(ctx), pageSize, page)
  241. headers := http.Header{}
  242. headers.Set("Authorization", "Bearer "+accessToken)
  243. return doRequest[[]*SProject](ctx, urlStr, httputils.GET, headers, nil)
  244. }
  245. func doRequest[Result any](ctx context.Context, urlStr string, method httputils.THttpMethod, headers http.Header, body json.JSONObject) (total int, result Result, err error) {
  246. httpclient := httputils.GetDefaultClient()
  247. repsHeaders, resp, err := httputils.JSONRequest(httpclient, ctx, method, urlStr, headers, body, true)
  248. if err != nil {
  249. return 0, result, err
  250. }
  251. total, _ = strconv.Atoi(repsHeaders.Get("X-Total-Count"))
  252. err = resp.Unmarshal(&result)
  253. return
  254. }