cloudgroup.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  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 models
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "gopkg.in/fatih/set.v0"
  20. "yunion.io/x/cloudmux/pkg/apis/cloudid"
  21. "yunion.io/x/cloudmux/pkg/cloudprovider"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/util/compare"
  26. "yunion.io/x/pkg/util/httputils"
  27. "yunion.io/x/sqlchemy"
  28. "yunion.io/x/onecloud/pkg/apis"
  29. api "yunion.io/x/onecloud/pkg/apis/cloudid"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/validators"
  34. "yunion.io/x/onecloud/pkg/cloudid/options"
  35. "yunion.io/x/onecloud/pkg/httperrors"
  36. "yunion.io/x/onecloud/pkg/mcclient"
  37. "yunion.io/x/onecloud/pkg/util/stringutils2"
  38. )
  39. type SCloudgroupManager struct {
  40. db.SStatusInfrasResourceBaseManager
  41. SCloudaccountResourceBaseManager
  42. SCloudproviderResourceBaseManager
  43. }
  44. var CloudgroupManager *SCloudgroupManager
  45. func init() {
  46. CloudgroupManager = &SCloudgroupManager{
  47. SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager(
  48. SCloudgroup{},
  49. "cloudgroups_tbl",
  50. "cloudgroup",
  51. "cloudgroups",
  52. ),
  53. }
  54. CloudgroupManager.SetVirtualObject(CloudgroupManager)
  55. }
  56. type SCloudgroup struct {
  57. db.SStatusInfrasResourceBase
  58. db.SExternalizedResourceBase
  59. SCloudaccountResourceBase
  60. SCloudproviderResourceBase
  61. }
  62. // 权限组列表
  63. func (manager *SCloudgroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudgroupListInput) (*sqlchemy.SQuery, error) {
  64. var err error
  65. q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput)
  66. if err != nil {
  67. return nil, err
  68. }
  69. q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput)
  70. if err != nil {
  71. return nil, err
  72. }
  73. q, err = manager.SCloudproviderResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudproviderResourceListInput)
  74. if err != nil {
  75. return nil, err
  76. }
  77. if len(query.ClouduserId) > 0 {
  78. _, err = ClouduserManager.FetchById(query.ClouduserId)
  79. if err != nil {
  80. if errors.Cause(err) == sql.ErrNoRows {
  81. return nil, httperrors.NewResourceNotFoundError2("clouduser", query.ClouduserId)
  82. }
  83. return q, httperrors.NewGeneralError(errors.Wrap(err, "ClouduserManager.FetchById"))
  84. }
  85. sq := CloudgroupUserManager.Query("cloudgroup_id").Equals("clouduser_id", query.ClouduserId)
  86. q = q.In("id", sq.SubQuery())
  87. }
  88. if len(query.CloudpolicyId) > 0 {
  89. _, err = CloudpolicyManager.FetchById(query.CloudpolicyId)
  90. if err != nil {
  91. if errors.Cause(err) == sql.ErrNoRows {
  92. return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", query.CloudpolicyId)
  93. }
  94. return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudpolicyManager.FetchById"))
  95. }
  96. sq := CloudgroupPolicyManager.Query("cloudgroup_id").Equals("cloudpolicy_id", query.CloudpolicyId)
  97. q = q.In("id", sq.SubQuery())
  98. }
  99. return q, nil
  100. }
  101. func (self *SCloudgroup) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  102. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupDeleteTask", self, userCred, nil, parentTaskId, "", nil)
  103. if err != nil {
  104. return errors.Wrap(err, "NewTask")
  105. }
  106. self.SetStatus(ctx, userCred, apis.STATUS_DELETING, "")
  107. return task.ScheduleRun(nil)
  108. }
  109. func (self *SCloudgroup) StartSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  110. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSyncstatusTask", self, userCred, nil, parentTaskId, "", nil)
  111. if err != nil {
  112. return errors.Wrap(err, "NewTask")
  113. }
  114. self.SetStatus(ctx, userCred, apis.STATUS_SYNC_STATUS, "")
  115. return task.ScheduleRun(nil)
  116. }
  117. func (manager *SCloudgroupManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
  118. accountId, _ := data.GetString("cloudaccount_id")
  119. managerId, _ := data.GetString("manager_id")
  120. return jsonutils.Marshal(map[string]string{"cloudaccount_id": accountId, "manager_id": managerId})
  121. }
  122. func (manager *SCloudgroupManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
  123. accountId, _ := values.GetString("cloudaccount_id")
  124. if len(accountId) > 0 {
  125. q = q.Equals("cloudaccount_id", accountId)
  126. }
  127. providerId, _ := values.GetString("manager_id")
  128. if len(providerId) > 0 {
  129. q = q.Equals("manager_id", providerId)
  130. }
  131. return q
  132. }
  133. // 更新权限组
  134. func (self *SCloudgroup) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupUpdateInput) (api.CloudgroupUpdateInput, error) {
  135. return input, nil
  136. }
  137. // 获取权限组详情
  138. func (manager *SCloudgroupManager) FetchCustomizeColumns(
  139. ctx context.Context,
  140. userCred mcclient.TokenCredential,
  141. query jsonutils.JSONObject,
  142. objs []interface{},
  143. fields stringutils2.SSortedStrings,
  144. isList bool,
  145. ) []api.CloudgroupDetails {
  146. rows := make([]api.CloudgroupDetails, len(objs))
  147. statusRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  148. acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  149. mRows := manager.SCloudproviderResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  150. groupIds := make([]string, len(objs))
  151. for i := range rows {
  152. rows[i] = api.CloudgroupDetails{
  153. StatusInfrasResourceBaseDetails: statusRows[i],
  154. CloudaccountResourceDetails: acRows[i],
  155. CloudproviderResourceDetails: mRows[i],
  156. Cloudpolicies: []api.SCloudIdBaseResource{},
  157. }
  158. group := objs[i].(*SCloudgroup)
  159. groupIds[i] = group.Id
  160. }
  161. userSQ := ClouduserManager.Query().SubQuery()
  162. ugQ := CloudgroupUserManager.Query().SubQuery()
  163. q := userSQ.Query(
  164. userSQ.Field("id"),
  165. userSQ.Field("name"),
  166. ugQ.Field("cloudgroup_id"),
  167. ).
  168. Join(ugQ, sqlchemy.Equals(ugQ.Field("clouduser_id"), userSQ.Field("id"))).
  169. Filter(sqlchemy.In(ugQ.Field("cloudgroup_id"), groupIds))
  170. userInfo := []struct {
  171. Id string
  172. Name string
  173. CloudgroupId string
  174. }{}
  175. err := q.All(&userInfo)
  176. if err != nil {
  177. log.Errorf("query group user info error: %v", err)
  178. return rows
  179. }
  180. users := map[string][]api.SCloudIdBaseResource{}
  181. for _, user := range userInfo {
  182. _, ok := users[user.CloudgroupId]
  183. if !ok {
  184. users[user.CloudgroupId] = []api.SCloudIdBaseResource{}
  185. }
  186. users[user.CloudgroupId] = append(users[user.CloudgroupId], api.SCloudIdBaseResource{
  187. Id: user.Id,
  188. Name: user.Name,
  189. })
  190. }
  191. policySQ := CloudpolicyManager.Query().SubQuery()
  192. pgQ := CloudgroupPolicyManager.Query().SubQuery()
  193. q = policySQ.Query(
  194. policySQ.Field("id"),
  195. policySQ.Field("name"),
  196. pgQ.Field("cloudgroup_id"),
  197. ).
  198. Join(pgQ, sqlchemy.Equals(pgQ.Field("cloudpolicy_id"), policySQ.Field("id"))).
  199. Filter(sqlchemy.In(pgQ.Field("cloudgroup_id"), groupIds))
  200. policyInfo := []struct {
  201. Id string
  202. Name string
  203. CloudgroupId string
  204. }{}
  205. err = q.All(&policyInfo)
  206. if err != nil {
  207. log.Errorf("query group policy info error: %v", err)
  208. return rows
  209. }
  210. policies := map[string][]api.SCloudIdBaseResource{}
  211. for _, policy := range policyInfo {
  212. _, ok := policies[policy.CloudgroupId]
  213. if !ok {
  214. policies[policy.CloudgroupId] = []api.SCloudIdBaseResource{}
  215. }
  216. policies[policy.CloudgroupId] = append(policies[policy.CloudgroupId], api.SCloudIdBaseResource{
  217. Id: policy.Id,
  218. Name: policy.Name,
  219. })
  220. }
  221. for i := range rows {
  222. rows[i].Cloudusers, _ = users[groupIds[i]]
  223. rows[i].ClouduserCount = len(rows[i].Cloudusers)
  224. rows[i].Cloudpolicies, _ = policies[groupIds[i]]
  225. rows[i].CloudpolicyCount = len(rows[i].Cloudpolicies)
  226. }
  227. return rows
  228. }
  229. func (manager *SCloudgroupManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  230. switch field {
  231. case "manager":
  232. managerQuery := CloudproviderManager.Query("name", "id").SubQuery()
  233. q.AppendField(managerQuery.Field("name", field)).Distinct()
  234. q = q.Join(managerQuery, sqlchemy.Equals(q.Field("manager_id"), managerQuery.Field("id")))
  235. return q, nil
  236. case "account":
  237. accountQuery := CloudaccountManager.Query("name", "id").SubQuery()
  238. providers := CloudproviderManager.Query("id", "cloudaccount_id").SubQuery()
  239. q.AppendField(accountQuery.Field("name", field)).Distinct()
  240. q = q.Join(providers, sqlchemy.Equals(q.Field("manager_id"), providers.Field("id")))
  241. q = q.Join(accountQuery, sqlchemy.Equals(providers.Field("cloudaccount_id"), accountQuery.Field("id")))
  242. return q, nil
  243. case "provider", "brand":
  244. accountQuery := CloudaccountManager.Query(field, "id").Distinct().SubQuery()
  245. providers := CloudproviderManager.Query("id", "cloudaccount_id").SubQuery()
  246. q.AppendField(accountQuery.Field(field)).Distinct()
  247. q = q.Join(providers, sqlchemy.Equals(q.Field("manager_id"), providers.Field("id")))
  248. q = q.Join(accountQuery, sqlchemy.Equals(providers.Field("cloudaccount_id"), accountQuery.Field("id")))
  249. return q, nil
  250. }
  251. return q, httperrors.ErrNotFound
  252. }
  253. // 创建权限组
  254. func (manager *SCloudgroupManager) ValidateCreateData(
  255. ctx context.Context,
  256. userCred mcclient.TokenCredential,
  257. ownerId mcclient.IIdentityProvider,
  258. query jsonutils.JSONObject,
  259. input *api.CloudgroupCreateInput,
  260. ) (*api.CloudgroupCreateInput, error) {
  261. if len(input.ManagerId) == 0 {
  262. return nil, httperrors.NewMissingParameterError("manager_id")
  263. }
  264. providerObj, err := validators.ValidateModel(ctx, userCred, CloudproviderManager, &input.ManagerId)
  265. if err != nil {
  266. return nil, err
  267. }
  268. provider := providerObj.(*SCloudprovider)
  269. input.CloudaccountId = provider.CloudaccountId
  270. driver, err := provider.GetDriver()
  271. if err != nil {
  272. return nil, err
  273. }
  274. input, err = driver.ValidateCreateCloudgroup(ctx, userCred, provider, input)
  275. if err != nil {
  276. return nil, err
  277. }
  278. input.Status = apis.STATUS_CREATING
  279. return input, nil
  280. }
  281. func (self *SCloudgroup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  282. input := api.CloudgroupCreateInput{}
  283. data.Unmarshal(&input)
  284. for _, policyId := range input.CloudpolicyIds {
  285. self.attachPolicy(policyId)
  286. }
  287. self.StartCreateTask(ctx, userCred, "")
  288. }
  289. func (self *SCloudgroup) StartCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  290. params := jsonutils.NewDict()
  291. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupCreateTask", self, userCred, params, parentTaskId, "", nil)
  292. if err != nil {
  293. return errors.Wrapf(err, "NewTask")
  294. }
  295. return task.ScheduleRun(nil)
  296. }
  297. // 删除权限组
  298. func (self *SCloudgroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  299. params := jsonutils.NewDict()
  300. return self.StartCloudgroupDeleteTask(ctx, userCred, params, "")
  301. }
  302. func (self *SCloudgroup) StartCloudgroupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
  303. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupDeleteTask", self, userCred, data, parentTaskId, "", nil)
  304. if err != nil {
  305. return errors.Wrap(err, "NewTask")
  306. }
  307. self.SetStatus(ctx, userCred, apis.STATUS_DELETING, "")
  308. return task.ScheduleRun(nil)
  309. }
  310. func (self *SCloudgroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  311. return nil
  312. }
  313. func (self *SCloudgroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
  314. return self.purge(ctx)
  315. }
  316. func (self *SCloudgroup) GetSamlusers() ([]SSamluser, error) {
  317. q := SamluserManager.Query().Equals("cloudgroup_id", self.Id)
  318. users := []SSamluser{}
  319. err := db.FetchModelObjects(SamluserManager, q, &users)
  320. if err != nil {
  321. return nil, errors.Wrapf(err, "db.FetchModelObjects")
  322. }
  323. return users, nil
  324. }
  325. func (self *SCloudgroup) GetCloudpolicyQuery() *sqlchemy.SQuery {
  326. sq := CloudgroupPolicyManager.Query("cloudpolicy_id").Equals("cloudgroup_id", self.Id).SubQuery()
  327. return CloudpolicyManager.Query().In("id", sq)
  328. }
  329. func (self *SCloudgroup) GetCloudpolicyCount() (int, error) {
  330. return self.GetCloudpolicyQuery().CountWithError()
  331. }
  332. func (self *SCloudgroup) GetCloudpolicies() ([]SCloudpolicy, error) {
  333. policies := []SCloudpolicy{}
  334. q := self.GetCloudpolicyQuery()
  335. err := db.FetchModelObjects(CloudpolicyManager, q, &policies)
  336. if err != nil {
  337. return nil, errors.Wrap(err, "db.FetchModelObjects")
  338. }
  339. return policies, nil
  340. }
  341. func (self *SCloudgroup) GetCloudpolicy(policyId string) (*SCloudpolicy, error) {
  342. policies := []SCloudpolicy{}
  343. q := self.GetCloudpolicyQuery().Equals("id", policyId)
  344. err := db.FetchModelObjects(CloudpolicyManager, q, &policies)
  345. if err != nil {
  346. return nil, errors.Wrap(err, "db.FetchModelObjects")
  347. }
  348. if len(policies) > 1 {
  349. return nil, sqlchemy.ErrDuplicateEntry
  350. }
  351. if len(policies) == 0 {
  352. return nil, sql.ErrNoRows
  353. }
  354. return &policies[0], nil
  355. }
  356. func (self *SCloudgroup) detachPolicy(policyId string) error {
  357. policies := []SCloudgroupPolicy{}
  358. q := CloudgroupPolicyManager.Query().Equals("cloudgroup_id", self.Id).Equals("cloudpolicy_id", policyId)
  359. err := db.FetchModelObjects(CloudgroupPolicyManager, q, &policies)
  360. if err != nil {
  361. return errors.Wrap(err, "db.FetchModelObjects")
  362. }
  363. for i := range policies {
  364. err = policies[i].Delete(context.Background(), nil)
  365. if err != nil {
  366. return errors.Wrap(err, "Delete")
  367. }
  368. }
  369. return nil
  370. }
  371. func (self *SCloudgroup) GetClouduserQuery() *sqlchemy.SQuery {
  372. sq := CloudgroupUserManager.Query("clouduser_id").Equals("cloudgroup_id", self.Id).SubQuery()
  373. return ClouduserManager.Query().In("id", sq)
  374. }
  375. func (self *SCloudgroup) GetClouduserCount() (int, error) {
  376. return self.GetClouduserQuery().CountWithError()
  377. }
  378. func (self *SCloudgroup) GetClouduser(userId string) (*SClouduser, error) {
  379. users := []SClouduser{}
  380. q := self.GetClouduserQuery().Equals("id", userId)
  381. err := db.FetchModelObjects(ClouduserManager, q, &users)
  382. if err != nil {
  383. return nil, errors.Wrap(err, "db.FetchModelObjects")
  384. }
  385. if len(users) > 1 {
  386. return nil, sqlchemy.ErrDuplicateEntry
  387. }
  388. if len(users) == 0 {
  389. return nil, sql.ErrNoRows
  390. }
  391. return &users[0], nil
  392. }
  393. func (self *SCloudgroup) GetCloudusers() ([]SClouduser, error) {
  394. users := []SClouduser{}
  395. q := self.GetClouduserQuery()
  396. err := db.FetchModelObjects(ClouduserManager, q, &users)
  397. if err != nil {
  398. return nil, errors.Wrap(err, "db.FetchModelObjects")
  399. }
  400. return users, nil
  401. }
  402. func (self *SCloudgroup) removeUser(userId string) error {
  403. users := []SCloudgroupUser{}
  404. q := CloudgroupUserManager.Query().Equals("cloudgroup_id", self.Id).Equals("clouduser_id", userId)
  405. err := db.FetchModelObjects(CloudgroupUserManager, q, &users)
  406. if err != nil {
  407. return errors.Wrap(err, "db.FetchModelObjects")
  408. }
  409. for i := range users {
  410. err = users[i].Delete(context.Background(), nil)
  411. if err != nil {
  412. return errors.Wrap(err, "Delete")
  413. }
  414. }
  415. return nil
  416. }
  417. // 向权限组加入用户
  418. // 权限组状态必须为: available
  419. func (self *SCloudgroup) PerformAddUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupAddUserInput) (jsonutils.JSONObject, error) {
  420. if self.Status != apis.STATUS_AVAILABLE {
  421. return nil, httperrors.NewInvalidStatusError("Can not remove user in status %s", self.Status)
  422. }
  423. userObj, err := validators.ValidateModel(ctx, userCred, ClouduserManager, &input.ClouduserId)
  424. if err != nil {
  425. return nil, err
  426. }
  427. user := userObj.(*SClouduser)
  428. if user.ManagerId != self.ManagerId || user.CloudaccountId != self.CloudaccountId {
  429. return nil, httperrors.NewConflictError("Users and user groups do not belong to the same account")
  430. }
  431. _, err = self.GetClouduser(input.ClouduserId)
  432. if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry {
  433. return nil, httperrors.NewDuplicateResourceError("user %s has aleady in this group", input.ClouduserId)
  434. }
  435. add := []api.GroupUser{
  436. {
  437. Name: user.Name,
  438. ExternalId: user.ExternalId,
  439. },
  440. }
  441. return nil, self.StartSetUsersTask(ctx, userCred, add, nil, "")
  442. }
  443. func (self *SCloudgroup) StartSetUsersTask(ctx context.Context, userCred mcclient.TokenCredential, add, del []api.GroupUser, parentTaskId string) error {
  444. params := jsonutils.NewDict()
  445. if len(add) > 0 {
  446. params.Set("add", jsonutils.Marshal(add))
  447. }
  448. if len(del) > 0 {
  449. params.Set("del", jsonutils.Marshal(del))
  450. }
  451. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSetUsersTask", self, userCred, params, parentTaskId, "", nil)
  452. if err != nil {
  453. return errors.Wrap(err, "NewTask")
  454. }
  455. self.SetStatus(ctx, userCred, apis.STATUS_SYNC_STATUS, "")
  456. return task.ScheduleRun(nil)
  457. }
  458. func (self *SCloudgroup) StartCloudgroupSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
  459. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSyncstatusTask", self, userCred, nil, parentTaskId, "", nil)
  460. if err != nil {
  461. return errors.Wrap(err, "NewTask")
  462. }
  463. self.SetStatus(ctx, userCred, apis.STATUS_SYNC_STATUS, "")
  464. return task.ScheduleRun(nil)
  465. }
  466. // 恢复权限组状态
  467. func (self *SCloudgroup) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSyncstatusInput) (jsonutils.JSONObject, error) {
  468. return nil, self.StartCloudgroupSyncstatusTask(ctx, userCred, "")
  469. }
  470. // 从权限组移除用户
  471. // 权限组状态必须为: available
  472. func (self *SCloudgroup) PerformRemoveUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupRemoveUserInput) (jsonutils.JSONObject, error) {
  473. if self.Status != apis.STATUS_AVAILABLE {
  474. return nil, httperrors.NewInvalidStatusError("Can not remove user in status %s", self.Status)
  475. }
  476. userObj, err := validators.ValidateModel(ctx, userCred, ClouduserManager, &input.ClouduserId)
  477. if err != nil {
  478. return nil, err
  479. }
  480. user := userObj.(*SClouduser)
  481. _, err = self.GetClouduser(input.ClouduserId)
  482. if err != nil {
  483. if errors.Cause(err) == sql.ErrNoRows {
  484. return nil, nil
  485. }
  486. return nil, httperrors.NewGeneralError(err)
  487. }
  488. del := []api.GroupUser{
  489. {
  490. Name: user.Name,
  491. ExternalId: user.ExternalId,
  492. },
  493. }
  494. return nil, self.StartSetUsersTask(ctx, userCred, nil, del, "")
  495. }
  496. func (self *SCloudgroup) addUser(userId string) error {
  497. gu := &SCloudgroupUser{}
  498. gu.SetModelManager(CloudgroupUserManager, gu)
  499. gu.ClouduserId = userId
  500. gu.CloudgroupId = self.Id
  501. return CloudgroupUserManager.TableSpec().Insert(context.Background(), gu)
  502. }
  503. // 设置权限组用户(全量覆盖)
  504. // 权限组状态必须为: available
  505. func (self *SCloudgroup) PerformSetUsers(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSetUsersInput) (jsonutils.JSONObject, error) {
  506. if self.Status != apis.STATUS_AVAILABLE {
  507. return nil, httperrors.NewInvalidStatusError("Can not set users in status %s", self.Status)
  508. }
  509. users, err := self.GetCloudusers()
  510. if err != nil {
  511. return nil, httperrors.NewGeneralError(err)
  512. }
  513. userMap := map[string]*SClouduser{}
  514. local := set.New(set.ThreadSafe)
  515. for i := range users {
  516. local.Add(users[i].Id)
  517. userMap[users[i].Id] = &users[i]
  518. }
  519. newU := set.New(set.ThreadSafe)
  520. for i := range input.ClouduserIds {
  521. _user, err := validators.ValidateModel(ctx, userCred, ClouduserManager, &input.ClouduserIds[i])
  522. if err != nil {
  523. return nil, err
  524. }
  525. user := _user.(*SClouduser)
  526. if user.ManagerId != self.ManagerId || user.CloudaccountId != self.CloudaccountId {
  527. return nil, httperrors.NewConflictError("Users and user groups do not belong to the same account")
  528. }
  529. newU.Add(user.Id)
  530. userMap[user.Id] = user
  531. }
  532. del, add := []api.GroupUser{}, []api.GroupUser{}
  533. for _, id := range set.Difference(local, newU).List() {
  534. user := userMap[id.(string)]
  535. del = append(del, api.GroupUser{
  536. Name: user.Name,
  537. ExternalId: user.ExternalId,
  538. })
  539. }
  540. for _, id := range set.Difference(newU, local).List() {
  541. user := userMap[id.(string)]
  542. add = append(add, api.GroupUser{
  543. Name: user.Name,
  544. ExternalId: user.ExternalId,
  545. })
  546. }
  547. return nil, self.StartSetUsersTask(ctx, userCred, add, del, "")
  548. }
  549. // 设置权限组添权限(全量覆盖)
  550. // 权限组状态必须为: available
  551. func (self *SCloudgroup) PerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSetPoliciesInput) (jsonutils.JSONObject, error) {
  552. if self.Status != apis.STATUS_AVAILABLE {
  553. return nil, httperrors.NewInvalidStatusError("Can not set policies in status %s", self.Status)
  554. }
  555. policies, err := self.GetCloudpolicies()
  556. if err != nil {
  557. return nil, httperrors.NewGeneralError(err)
  558. }
  559. policyMap := map[string]*SCloudpolicy{}
  560. local := set.New(set.ThreadSafe)
  561. for i := range policies {
  562. local.Add(policies[i].Id)
  563. policyMap[policies[i].Id] = &policies[i]
  564. }
  565. newP := set.New(set.ThreadSafe)
  566. for i := range input.CloudpolicyIds {
  567. policObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &input.CloudpolicyIds[i])
  568. if err != nil {
  569. return nil, err
  570. }
  571. policy := policObj.(*SCloudpolicy)
  572. if (policy.ManagerId != self.ManagerId && len(self.ManagerId) > 0) || policy.CloudaccountId != self.CloudaccountId {
  573. return nil, httperrors.NewConflictError("Policies and groups do not belong to the same account")
  574. }
  575. newP.Add(policy.Id)
  576. policyMap[policy.Id] = policy
  577. }
  578. del, add := []api.SPolicy{}, []api.SPolicy{}
  579. for _, id := range set.Difference(local, newP).List() {
  580. policy := policyMap[id.(string)]
  581. del = append(del, api.SPolicy{
  582. Name: policy.Name,
  583. ExternalId: policy.ExternalId,
  584. PolicyType: policy.PolicyType,
  585. })
  586. }
  587. for _, id := range set.Difference(newP, local).List() {
  588. policy := policyMap[id.(string)]
  589. add = append(add, api.SPolicy{
  590. Name: policy.Name,
  591. ExternalId: policy.ExternalId,
  592. PolicyType: policy.PolicyType,
  593. })
  594. }
  595. return nil, self.StartSetPoliciesTask(ctx, userCred, add, del, "")
  596. }
  597. func (self *SCloudgroup) StartSetPoliciesTask(ctx context.Context, userCred mcclient.TokenCredential, add, del []api.SPolicy, parentTaskId string) error {
  598. params := jsonutils.NewDict()
  599. if len(add) > 0 {
  600. params.Set("add", jsonutils.Marshal(add))
  601. }
  602. if len(del) > 0 {
  603. params.Set("del", jsonutils.Marshal(del))
  604. }
  605. task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSetPoliciesTask", self, userCred, params, parentTaskId, "", nil)
  606. if err != nil {
  607. return errors.Wrap(err, "NewTask")
  608. }
  609. self.SetStatus(ctx, userCred, apis.STATUS_SYNC_STATUS, "")
  610. return task.ScheduleRun(nil)
  611. }
  612. // 向权限组添加权限
  613. // 权限组状态必须为: available
  614. func (self *SCloudgroup) PerformAttachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupAttachPolicyInput) (jsonutils.JSONObject, error) {
  615. if self.Status != apis.STATUS_AVAILABLE {
  616. return nil, httperrors.NewInvalidStatusError("Can not attach policy in status %s", self.Status)
  617. }
  618. policyObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &input.CloudpolicyId)
  619. if err != nil {
  620. return nil, err
  621. }
  622. policy := policyObj.(*SCloudpolicy)
  623. if policy.ManagerId != self.ManagerId || policy.CloudaccountId != self.CloudaccountId {
  624. return nil, httperrors.NewConflictError("policy and groups do not belong to the same account")
  625. }
  626. _, err = self.GetCloudpolicy(input.CloudpolicyId)
  627. if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry {
  628. return nil, httperrors.NewDuplicateResourceError("policy %s has aleady in this group", input.CloudpolicyId)
  629. }
  630. add := []api.SPolicy{
  631. {
  632. Name: policy.Name,
  633. ExternalId: policy.ExternalId,
  634. PolicyType: policy.PolicyType,
  635. },
  636. }
  637. return nil, self.StartSetPoliciesTask(ctx, userCred, add, nil, "")
  638. }
  639. // 从权限组移除权限
  640. // 权限组状态必须为: available
  641. func (self *SCloudgroup) PerformDetachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupDetachPolicyInput) (jsonutils.JSONObject, error) {
  642. if self.Status != apis.STATUS_AVAILABLE {
  643. return nil, httperrors.NewInvalidStatusError("Can not detach policy in status %s", self.Status)
  644. }
  645. policObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &input.CloudpolicyId)
  646. if err != nil {
  647. return nil, err
  648. }
  649. policy := policObj.(*SCloudpolicy)
  650. _, err = self.GetCloudpolicy(input.CloudpolicyId)
  651. if err != nil && errors.Cause(err) == sql.ErrNoRows {
  652. return nil, nil
  653. }
  654. del := []api.SPolicy{
  655. {
  656. Name: policy.Name,
  657. ExternalId: policy.ExternalId,
  658. PolicyType: policy.PolicyType,
  659. },
  660. }
  661. return nil, self.StartSetPoliciesTask(ctx, userCred, nil, del, "")
  662. }
  663. func (self *SCloudgroup) attachPolicy(policyId string) error {
  664. gp := &SCloudgroupPolicy{}
  665. gp.SetModelManager(CloudgroupPolicyManager, gp)
  666. gp.CloudpolicyId = policyId
  667. gp.CloudgroupId = self.Id
  668. return CloudgroupPolicyManager.TableSpec().Insert(context.Background(), gp)
  669. }
  670. func (self *SCloudaccount) SyncCloudgroups(ctx context.Context, userCred mcclient.TokenCredential, iGroups []cloudprovider.ICloudgroup, managerId string) ([]SCloudgroup, []cloudprovider.ICloudgroup, compare.SyncResult) {
  671. lockman.LockRawObject(ctx, CloudgroupManager.Keyword(), fmt.Sprintf("%s-%s", self.Id, managerId))
  672. defer lockman.ReleaseRawObject(ctx, CloudgroupManager.Keyword(), fmt.Sprintf("%s-%s", self.Id, managerId))
  673. result := compare.SyncResult{}
  674. dbGroups, err := self.GetCloudgroups(managerId)
  675. if err != nil {
  676. result.Error(errors.Wrap(err, "GetCloudgroups"))
  677. return nil, nil, result
  678. }
  679. localGroups := []SCloudgroup{}
  680. remoteGroups := []cloudprovider.ICloudgroup{}
  681. removed := make([]SCloudgroup, 0)
  682. commondb := make([]SCloudgroup, 0)
  683. commonext := make([]cloudprovider.ICloudgroup, 0)
  684. added := make([]cloudprovider.ICloudgroup, 0)
  685. err = compare.CompareSets(dbGroups, iGroups, &removed, &commondb, &commonext, &added)
  686. if err != nil {
  687. result.Error(errors.Wrap(err, "compare.CompareSets"))
  688. return nil, nil, result
  689. }
  690. for i := 0; i < len(removed); i++ {
  691. err = removed[i].RealDelete(ctx, userCred)
  692. if err != nil {
  693. result.DeleteError(err)
  694. continue
  695. }
  696. result.Delete()
  697. }
  698. for i := 0; i < len(commondb); i++ {
  699. err = commondb[i].SyncWithCloudgroup(ctx, userCred, commonext[i])
  700. if err != nil {
  701. result.UpdateError(err)
  702. continue
  703. }
  704. localGroups = append(localGroups, commondb[i])
  705. remoteGroups = append(remoteGroups, commonext[i])
  706. result.Update()
  707. }
  708. for i := 0; i < len(added); i++ {
  709. group, err := self.newCloudgroup(ctx, userCred, added[i], managerId)
  710. if err != nil {
  711. result.AddError(err)
  712. continue
  713. }
  714. localGroups = append(localGroups, *group)
  715. remoteGroups = append(remoteGroups, added[i])
  716. result.Add()
  717. }
  718. return localGroups, remoteGroups, result
  719. }
  720. func (group *SCloudgroup) GetCloudprovider() (*SCloudprovider, error) {
  721. provider, err := CloudproviderManager.FetchById(group.ManagerId)
  722. if err != nil {
  723. return nil, err
  724. }
  725. return provider.(*SCloudprovider), nil
  726. }
  727. func (self *SCloudgroup) GetProvider() (cloudprovider.ICloudProvider, error) {
  728. if len(self.ManagerId) > 0 {
  729. provider, err := self.GetCloudprovider()
  730. if err != nil {
  731. return nil, err
  732. }
  733. return provider.GetProvider()
  734. }
  735. if len(self.CloudaccountId) > 0 {
  736. account, err := self.GetCloudaccount()
  737. if err != nil {
  738. if err != nil {
  739. return nil, err
  740. }
  741. }
  742. return account.GetProvider()
  743. }
  744. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty account info")
  745. }
  746. func (group *SCloudgroup) GetICloudgroup() (cloudprovider.ICloudgroup, error) {
  747. if len(group.ExternalId) == 0 {
  748. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty external id")
  749. }
  750. provider, err := group.GetProvider()
  751. if err != nil {
  752. return nil, err
  753. }
  754. groups, err := provider.GetICloudgroups()
  755. if err != nil {
  756. return nil, err
  757. }
  758. for i := range groups {
  759. if groups[i].GetGlobalId() == group.ExternalId {
  760. return groups[i], nil
  761. }
  762. }
  763. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%v", group.ExternalId)
  764. }
  765. func (group *SCloudgroup) SyncWithCloudgroup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup) error {
  766. _, err := db.Update(group, func() error {
  767. group.Name = iGroup.GetName()
  768. group.Status = apis.STATUS_AVAILABLE
  769. return nil
  770. })
  771. return err
  772. }
  773. func (self *SCloudaccount) newCloudgroup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup, managerId string) (*SCloudgroup, error) {
  774. group := &SCloudgroup{}
  775. group.SetModelManager(CloudgroupManager, group)
  776. group.Name = iGroup.GetName()
  777. group.ExternalId = iGroup.GetGlobalId()
  778. group.Status = apis.STATUS_AVAILABLE
  779. group.CloudaccountId = self.Id
  780. group.ManagerId = managerId
  781. group.DomainId = self.DomainId
  782. err := CloudgroupManager.TableSpec().Insert(ctx, group)
  783. if err != nil {
  784. return nil, errors.Wrap(err, "Insert")
  785. }
  786. return group, nil
  787. }
  788. func (self *SCloudgroup) SyncCloudusers(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup) {
  789. iUsers, err := iGroup.GetICloudusers()
  790. if err == nil {
  791. result := self.SyncUsers(ctx, userCred, iUsers)
  792. log.Debugf("sync cloudusers for group %s result: %s", self.Name, result.Result())
  793. }
  794. }
  795. func (self *SCloudgroup) SyncUsers(ctx context.Context, userCred mcclient.TokenCredential, iUsers []cloudprovider.IClouduser) compare.SyncResult {
  796. lockman.LockRawObject(ctx, ClouduserManager.Keyword(), self.Id)
  797. defer lockman.ReleaseRawObject(ctx, ClouduserManager.Keyword(), self.Id)
  798. result := compare.SyncResult{}
  799. dbUsers, err := self.GetCloudusers()
  800. if err != nil {
  801. result.Error(errors.Wrap(err, "GetCloudusers"))
  802. return result
  803. }
  804. removed := make([]SClouduser, 0)
  805. commondb := make([]SClouduser, 0)
  806. commonext := make([]cloudprovider.IClouduser, 0)
  807. added := make([]cloudprovider.IClouduser, 0)
  808. err = compare.CompareSets(dbUsers, iUsers, &removed, &commondb, &commonext, &added)
  809. if err != nil {
  810. result.Error(errors.Wrap(err, "compare.CompareSets"))
  811. return result
  812. }
  813. for i := 0; i < len(removed); i++ {
  814. self.removeUser(removed[i].Id)
  815. if err != nil {
  816. result.DeleteError(err)
  817. continue
  818. }
  819. result.Delete()
  820. }
  821. result.UpdateCnt = len(commondb)
  822. for i := 0; i < len(added); i++ {
  823. user, err := db.FetchByExternalIdAndManagerId(ClouduserManager, added[i].GetGlobalId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  824. if len(self.ManagerId) > 0 {
  825. return q.Equals("manager_id", self.ManagerId)
  826. }
  827. return q.Equals("cloudaccount_id", self.CloudaccountId)
  828. })
  829. if err != nil {
  830. result.AddError(errors.Wrapf(err, "Fetch %s", added[i].GetGlobalId()))
  831. continue
  832. }
  833. err = self.addUser(user.GetId())
  834. if err != nil {
  835. result.AddError(err)
  836. continue
  837. }
  838. result.Add()
  839. }
  840. return result
  841. }
  842. func (self *SCloudgroup) SyncCloudpolicies(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup) {
  843. iPolicies, err := iGroup.GetICloudpolicies()
  844. if err == nil {
  845. result := self.SyncPolicies(ctx, userCred, iPolicies)
  846. log.Infof("SyncCloudpolicies for group %s(%s) result: %s", self.Name, self.Id, result.Result())
  847. }
  848. }
  849. func (self *SCloudgroup) SyncPolicies(ctx context.Context, userCred mcclient.TokenCredential, iPolicies []cloudprovider.ICloudpolicy) compare.SyncResult {
  850. result := compare.SyncResult{}
  851. dbPolicies, err := self.GetCloudpolicies()
  852. if err != nil {
  853. result.Error(errors.Wrapf(err, "GetCloudpolicies"))
  854. return result
  855. }
  856. removed := make([]SCloudpolicy, 0)
  857. commondb := make([]SCloudpolicy, 0)
  858. commonext := make([]cloudprovider.ICloudpolicy, 0)
  859. added := make([]cloudprovider.ICloudpolicy, 0)
  860. err = compare.CompareSets(dbPolicies, iPolicies, &removed, &commondb, &commonext, &added)
  861. if err != nil {
  862. result.Error(errors.Wrap(err, "compare.CompareSets"))
  863. return result
  864. }
  865. for i := 0; i < len(removed); i++ {
  866. err := self.detachPolicy(removed[i].Id)
  867. if err != nil {
  868. result.DeleteError(err)
  869. continue
  870. }
  871. result.Delete()
  872. }
  873. result.UpdateCnt = len(commondb)
  874. for i := 0; i < len(added); i++ {
  875. policy, err := db.FetchByExternalIdAndManagerId(CloudpolicyManager, added[i].GetGlobalId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  876. if len(self.ManagerId) > 0 {
  877. return q.Equals("manager_id", self.ManagerId)
  878. }
  879. return q.Equals("cloudaccount_id", self.CloudaccountId)
  880. })
  881. if err != nil {
  882. result.AddError(errors.Wrapf(err, "add %s(%s)", added[i].GetName(), added[i].GetGlobalId()))
  883. continue
  884. }
  885. err = self.attachPolicy(policy.GetId())
  886. if err != nil {
  887. result.AddError(err)
  888. continue
  889. }
  890. result.Add()
  891. }
  892. return result
  893. }
  894. func (self *SCloudgroup) GetSamlProvider() (*SSAMLProvider, error) {
  895. q := SAMLProviderManager.Query().Equals("status", apis.STATUS_AVAILABLE).
  896. Equals("entity_id", options.Options.ApiServer).
  897. Equals("cloudaccount_id", self.CloudaccountId).
  898. IsNotEmpty("external_id")
  899. if len(self.ManagerId) > 0 {
  900. q = q.Equals("manager_id", self.ManagerId)
  901. }
  902. ret := &SSAMLProvider{}
  903. ret.SetModelManager(SAMLProviderManager, ret)
  904. err := q.First(ret)
  905. if err != nil {
  906. return nil, err
  907. }
  908. return ret, nil
  909. }
  910. func (self *SCloudgroup) GetCloudroles() ([]SCloudrole, error) {
  911. sq := SamluserManager.Query("cloudrole_id").Equals("cloudgroup_id", self.Id).SubQuery()
  912. q := CloudroleManager.Query().Equals("cloudgroup_id", self.Id).IsNotEmpty("external_id").In("id", sq)
  913. ret := []SCloudrole{}
  914. err := db.FetchModelObjects(CloudroleManager, q, &ret)
  915. if err != nil {
  916. return nil, err
  917. }
  918. return ret, nil
  919. }
  920. func (self *SCloudgroup) GetDetailsSaml(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*api.GetCloudaccountSamlOutput, error) {
  921. output := &api.GetCloudaccountSamlOutput{}
  922. account, err := self.GetCloudaccount()
  923. if err != nil {
  924. return nil, err
  925. }
  926. if account.SAMLAuth.IsFalse() {
  927. return output, httperrors.NewNotSupportedError("account %s not enable saml auth", account.Name)
  928. }
  929. provider, err := account.GetProvider()
  930. if err != nil {
  931. return output, errors.Wrap(err, "GetProviderFactory")
  932. }
  933. samlProvider, err := self.GetSamlProvider()
  934. if err != nil {
  935. if errors.Cause(err) == sql.ErrNoRows {
  936. return nil, httperrors.NewResourceNotReadyError("no available saml provider")
  937. }
  938. return nil, errors.Wrapf(err, "GetSamlProvider")
  939. }
  940. output.EntityId = provider.GetSamlEntityId()
  941. if len(output.EntityId) == 0 {
  942. return output, errors.Wrap(httperrors.ErrNotSupported, "SAML login not supported")
  943. }
  944. id := self.CloudaccountId
  945. if len(self.ManagerId) > 0 {
  946. id = self.ManagerId
  947. }
  948. output.RedirectLoginUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/login", id)
  949. output.RedirectLogoutUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/logout", id)
  950. output.MetadataUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "metadata", id)
  951. output.InitLoginUrl = samlProvider.AuthUrl
  952. return output, nil
  953. }