assignments.go 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  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. "net/http"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/tristate"
  25. "yunion.io/x/pkg/util/rbacscope"
  26. "yunion.io/x/pkg/utils"
  27. "yunion.io/x/sqlchemy"
  28. api "yunion.io/x/onecloud/pkg/apis/identity"
  29. "yunion.io/x/onecloud/pkg/appsrv"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  31. "yunion.io/x/onecloud/pkg/httperrors"
  32. "yunion.io/x/onecloud/pkg/keystone/options"
  33. "yunion.io/x/onecloud/pkg/mcclient"
  34. "yunion.io/x/onecloud/pkg/mcclient/auth"
  35. "yunion.io/x/onecloud/pkg/util/stringutils2"
  36. )
  37. // +onecloud:swagger-gen-ignore
  38. type SAssignmentManager struct {
  39. db.SResourceBaseManager
  40. }
  41. var AssignmentManager *SAssignmentManager
  42. func init() {
  43. AssignmentManager = &SAssignmentManager{
  44. SResourceBaseManager: db.NewResourceBaseManager(
  45. SAssignment{},
  46. "assignment",
  47. "assignment",
  48. "assignments",
  49. ),
  50. }
  51. AssignmentManager.SetVirtualObject(AssignmentManager)
  52. }
  53. /*
  54. +-----------+---------------------------------------------------------------+------+-----+---------+-------+
  55. | Field | Type | Null | Key | Default | Extra |
  56. +-----------+---------------------------------------------------------------+------+-----+---------+-------+
  57. | type | enum('UserProject','GroupProject','UserDomain','GroupDomain') | NO | PRI | NULL | |
  58. | actor_id | varchar(64) | NO | PRI | NULL | |
  59. | target_id | varchar(64) | NO | PRI | NULL | |
  60. | role_id | varchar(64) | NO | PRI | NULL | |
  61. | inherited | tinyint(1) | NO | PRI | NULL | |
  62. +-----------+---------------------------------------------------------------+------+-----+---------+-------+
  63. */
  64. type SAssignment struct {
  65. db.SResourceBase
  66. // 关联类型,分为四类:'UserProject','GroupProject','UserDomain','GroupDomain'
  67. Type string `width:"16" charset:"ascii" nullable:"false" primary:"true" list:"admin"`
  68. // 用户或者用户组ID
  69. ActorId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"admin"`
  70. // 项目或者域ID
  71. TargetId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"admin"`
  72. // 角色ID
  73. RoleId string `width:"64" charset:"ascii" nullable:"false" primary:"true" list:"admin"`
  74. Inherited tristate.TriState `primary:"true" list:"admin"`
  75. }
  76. func (manager *SAssignmentManager) InitializeData() error {
  77. return manager.initSysAssignment(context.TODO())
  78. }
  79. func (manager *SAssignmentManager) initSysAssignment(ctx context.Context) error {
  80. adminUser, err := UserManager.FetchUserExtended("", api.SystemAdminUser, api.DEFAULT_DOMAIN_ID, "")
  81. if err != nil {
  82. return errors.Wrap(err, "FetchUserExtended")
  83. }
  84. adminProject, err := ProjectManager.FetchProjectByName(api.SystemAdminProject, api.DEFAULT_DOMAIN_ID, "")
  85. if err != nil {
  86. return errors.Wrap(err, "FetchProjectByName")
  87. }
  88. adminRole, err := RoleManager.FetchRoleByName(api.SystemAdminRole, api.DEFAULT_DOMAIN_ID, "")
  89. if err != nil {
  90. return errors.Wrap(err, "FetchRoleByName")
  91. }
  92. q := manager.Query().Equals("type", api.AssignmentUserProject)
  93. q = q.Equals("actor_id", adminUser.Id)
  94. q = q.Equals("target_id", adminProject.Id)
  95. q = q.Equals("role_id", adminRole.Id)
  96. q = q.IsFalse("inherited")
  97. assign := SAssignment{}
  98. assign.SetModelManager(manager, &assign)
  99. err = q.First(&assign)
  100. if err != nil && err != sql.ErrNoRows {
  101. return errors.Wrap(err, "query")
  102. }
  103. if err == nil {
  104. return nil
  105. }
  106. // no data
  107. assign.Type = api.AssignmentUserProject
  108. assign.ActorId = adminUser.Id
  109. assign.TargetId = adminProject.Id
  110. assign.RoleId = adminRole.Id
  111. assign.Inherited = tristate.False
  112. err = manager.TableSpec().Insert(ctx, &assign)
  113. if err != nil {
  114. return errors.Wrap(err, "insert")
  115. }
  116. return nil
  117. }
  118. func (manager *SAssignmentManager) fetchUserProjectRoleCount(userId, projId string) (int, error) {
  119. q := manager.fetchUserProjectRoleIdsQuery(userId, projId)
  120. return q.CountWithError()
  121. }
  122. func (manager *SAssignmentManager) fetchGroupProjectRoleCount(grpId, projId string) (int, error) {
  123. q := manager.fetchGroupProjectRoleIdsQuery(grpId, projId)
  124. return q.CountWithError()
  125. }
  126. func (manager *SAssignmentManager) FetchUserProjectRoles(userId, projId string) ([]SRole, error) {
  127. subq := manager.fetchUserProjectRoleIdsQuery(userId, projId)
  128. q := RoleManager.Query().In("id", subq.SubQuery())
  129. roles := make([]SRole, 0)
  130. err := db.FetchModelObjects(RoleManager, q, &roles)
  131. if err != nil && err != sql.ErrNoRows {
  132. return nil, err
  133. }
  134. return roles, nil
  135. }
  136. func (manager *SAssignmentManager) fetchRoleUserIdsQuery(roleId string) *sqlchemy.SQuery {
  137. q := manager.Query("actor_id").Equals("role_id", roleId).Equals("type", api.AssignmentUserProject).Distinct().SubQuery()
  138. return q.Query()
  139. }
  140. func (manager *SAssignmentManager) fetchRoleGroupIdsQuery(roleId string) *sqlchemy.SQuery {
  141. q := manager.Query("actor_id").Equals("role_id", roleId).Equals("type", api.AssignmentGroupProject).Distinct().SubQuery()
  142. return q.Query()
  143. }
  144. func (manager *SAssignmentManager) fetchRoleProjectIdsQuery(roleId string) *sqlchemy.SQuery {
  145. q := manager.Query("target_id").Equals("role_id", roleId).Distinct().SubQuery()
  146. return q.Query()
  147. }
  148. func (manager *SAssignmentManager) fetchUserProjectRoleIdsQuery(userId, projId string) *sqlchemy.SQuery {
  149. subq := AssignmentManager.Query("role_id")
  150. subq = subq.Equals("type", api.AssignmentUserProject)
  151. subq = subq.Equals("actor_id", userId)
  152. subq = subq.Equals("target_id", projId)
  153. subq = subq.IsFalse("inherited")
  154. assigns := AssignmentManager.Query().SubQuery()
  155. usergroups := UsergroupManager.Query().SubQuery()
  156. subq2 := assigns.Query(assigns.Field("role_id"))
  157. subq2 = subq2.Join(usergroups, sqlchemy.Equals(
  158. usergroups.Field("group_id"), assigns.Field("actor_id"),
  159. ))
  160. subq2 = subq2.Filter(sqlchemy.Equals(assigns.Field("type"), api.AssignmentGroupProject))
  161. subq2 = subq2.Filter(sqlchemy.Equals(assigns.Field("target_id"), projId))
  162. subq2 = subq2.Filter(sqlchemy.Equals(usergroups.Field("user_id"), userId))
  163. subq2 = subq2.Filter(sqlchemy.IsFalse(assigns.Field("inherited")))
  164. return sqlchemy.Union(subq, subq2).Query().Distinct()
  165. }
  166. func (manager *SAssignmentManager) fetchGroupProjectRoleIdsQuery(groupId, projId string) *sqlchemy.SQuery {
  167. subq := AssignmentManager.Query("role_id")
  168. subq = subq.Equals("type", api.AssignmentGroupProject)
  169. subq = subq.Equals("actor_id", groupId)
  170. subq = subq.Equals("target_id", projId)
  171. subq = subq.IsFalse("inherited")
  172. return subq.Distinct()
  173. }
  174. func (manager *SAssignmentManager) fetchGroupProjectIdsQuery(groupId string) *sqlchemy.SQuery {
  175. q := manager.Query("target_id")
  176. q = q.Equals("type", api.AssignmentGroupProject)
  177. q = q.Equals("actor_id", groupId)
  178. q = q.IsFalse("inherited")
  179. return q.Distinct()
  180. }
  181. func (manager *SAssignmentManager) fetchProjectGroupIdsQuery(projId string) *sqlchemy.SQuery {
  182. q := manager.Query("actor_id")
  183. q = q.Equals("type", api.AssignmentGroupProject)
  184. q = q.Equals("target_id", projId)
  185. q = q.IsFalse("inherited")
  186. return q.Distinct()
  187. }
  188. func (manager *SAssignmentManager) fetchUserProjectIdsQuery(userId string) *sqlchemy.SQuery {
  189. q1 := manager.Query("target_id")
  190. q1 = q1.Equals("type", api.AssignmentUserProject)
  191. q1 = q1.Equals("actor_id", userId)
  192. q1 = q1.IsFalse("inherited")
  193. assigns := AssignmentManager.Query().SubQuery()
  194. usergroups := UsergroupManager.Query().SubQuery()
  195. q2 := assigns.Query(assigns.Field("target_id"))
  196. q2 = q2.Join(usergroups, sqlchemy.Equals(
  197. usergroups.Field("group_id"), assigns.Field("actor_id"),
  198. ))
  199. q2 = q2.Filter(sqlchemy.Equals(assigns.Field("type"), api.AssignmentGroupProject))
  200. q2 = q2.Filter(sqlchemy.Equals(usergroups.Field("user_id"), userId))
  201. q2 = q2.Filter(sqlchemy.IsFalse(assigns.Field("inherited")))
  202. union := sqlchemy.Union(q1, q2)
  203. return union.Query().Distinct()
  204. }
  205. func (manager *SAssignmentManager) fetchProjectUserIdsQuery(projId string) *sqlchemy.SQuery {
  206. return manager.fetchProjectRoleUserIdsQuery(projId, "")
  207. }
  208. func (manager *SAssignmentManager) fetchProjectRoleUserIdsQuery(projId, roleId string) *sqlchemy.SQuery {
  209. q1 := manager.Query("actor_id")
  210. q1 = q1.Equals("type", api.AssignmentUserProject)
  211. q1 = q1.Equals("target_id", projId)
  212. q1 = q1.IsFalse("inherited")
  213. if len(roleId) > 0 {
  214. q1 = q1.Equals("role_id", roleId)
  215. }
  216. assigns := AssignmentManager.Query().SubQuery()
  217. usergroups := UsergroupManager.Query().SubQuery()
  218. q2 := usergroups.Query(usergroups.Field("user_id", "actor_id"))
  219. q2 = q2.Join(assigns, sqlchemy.Equals(
  220. usergroups.Field("group_id"), assigns.Field("actor_id"),
  221. ))
  222. q2 = q2.Filter(sqlchemy.Equals(assigns.Field("type"), api.AssignmentGroupProject))
  223. q2 = q2.Filter(sqlchemy.Equals(assigns.Field("target_id"), projId))
  224. q2 = q2.Filter(sqlchemy.IsFalse(assigns.Field("inherited")))
  225. if len(roleId) > 0 {
  226. q2 = q2.Equals("role_id", roleId)
  227. }
  228. union := sqlchemy.Union(q1, q2)
  229. return union.Query().Distinct()
  230. }
  231. func (manager *SAssignmentManager) fetchUserAndGroups(projIds []string) (map[string][]string, map[string][]string, error) {
  232. q1 := manager.Query().In("type", []string{api.AssignmentGroupProject, api.AssignmentUserProject}).IsFalse("inherited").In("target_id", projIds)
  233. groupCnt, userCnt := map[string][]string{}, map[string][]string{}
  234. assignments := []SAssignment{}
  235. err := q1.All(&assignments)
  236. if err != nil {
  237. return groupCnt, userCnt, errors.Wrapf(err, "q1.All")
  238. }
  239. for i := range assignments {
  240. switch assignments[i].Type {
  241. case api.AssignmentGroupProject:
  242. _, ok := groupCnt[assignments[i].TargetId]
  243. if !ok {
  244. groupCnt[assignments[i].TargetId] = []string{}
  245. }
  246. if !utils.IsInStringArray(assignments[i].ActorId, groupCnt[assignments[i].TargetId]) {
  247. groupCnt[assignments[i].TargetId] = append(groupCnt[assignments[i].TargetId], assignments[i].ActorId)
  248. }
  249. case api.AssignmentUserProject:
  250. _, ok := userCnt[assignments[i].TargetId]
  251. if !ok {
  252. userCnt[assignments[i].TargetId] = []string{}
  253. }
  254. if !utils.IsInStringArray(assignments[i].ActorId, userCnt[assignments[i].TargetId]) {
  255. userCnt[assignments[i].TargetId] = append(userCnt[assignments[i].TargetId], assignments[i].ActorId)
  256. }
  257. }
  258. }
  259. assigns := AssignmentManager.Query().SubQuery()
  260. usergroups := UsergroupManager.Query().SubQuery()
  261. q2 := usergroups.Query(usergroups.Field("user_id", "actor_id"))
  262. q2 = q2.Join(assigns, sqlchemy.Equals(
  263. usergroups.Field("group_id"), assigns.Field("actor_id"),
  264. ))
  265. q2 = q2.Filter(sqlchemy.Equals(assigns.Field("type"), api.AssignmentGroupProject))
  266. q2 = q2.Filter(sqlchemy.In(assigns.Field("target_id"), projIds))
  267. q2 = q2.Filter(sqlchemy.IsFalse(assigns.Field("inherited")))
  268. err = q2.All(&assignments)
  269. if err != nil {
  270. return groupCnt, userCnt, errors.Wrapf(err, "q2.All")
  271. }
  272. for i := range assignments {
  273. _, ok := userCnt[assignments[i].TargetId]
  274. if !ok {
  275. userCnt[assignments[i].TargetId] = []string{}
  276. }
  277. if !utils.IsInStringArray(assignments[i].ActorId, userCnt[assignments[i].TargetId]) {
  278. userCnt[assignments[i].TargetId] = append(userCnt[assignments[i].TargetId], assignments[i].ActorId)
  279. }
  280. }
  281. return groupCnt, userCnt, nil
  282. }
  283. func (manager *SAssignmentManager) ProjectAddUser(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, user *SUser, role *SRole) error {
  284. err := db.ValidateCreateDomainId(project.DomainId)
  285. if err != nil {
  286. return err
  287. }
  288. if project.DomainId != user.DomainId {
  289. // if project.DomainId != api.DEFAULT_DOMAIN_ID && !options.Options.AllowJoinProjectsAcrossDomains {
  290. // return httperrors.NewInputParameterError("join user into project of default domain or identical domain")
  291. // } else
  292. if !db.IsAllowPerform(ctx, rbacscope.ScopeSystem, userCred, user, "join-project") {
  293. return httperrors.NewForbiddenError("not enough privilege")
  294. }
  295. } else {
  296. if !db.IsAllowPerform(ctx, rbacscope.ScopeDomain, userCred, user, "join-project") {
  297. return httperrors.NewForbiddenError("not enough privilege")
  298. }
  299. }
  300. roleCnt, err := manager.fetchUserProjectRoleCount(user.Id, project.Id)
  301. if err != nil {
  302. return errors.Wrap(err, "FetchUserProjectRoleCount")
  303. }
  304. if roleCnt >= options.Options.MaxUserRolesInProject {
  305. return errors.Wrapf(httperrors.ErrTooLarge, "user %s has joined project %s %d roles more than %d", user.Name, project.Name, roleCnt, options.Options.MaxUserRolesInProject)
  306. }
  307. err = manager.add(ctx, api.AssignmentUserProject, user.Id, project.Id, role.Id)
  308. if err != nil {
  309. return errors.Wrap(err, "manager.add")
  310. }
  311. db.OpsLog.LogEvent(user, db.ACT_ATTACH, project.GetShortDesc(ctx), userCred)
  312. db.OpsLog.LogEvent(project, db.ACT_ATTACH, user.GetShortDesc(ctx), userCred)
  313. if len(project.AdminId) == 0 && role.Name == options.Options.ProjectAdminRole {
  314. err := project.resetAdminUser(ctx, userCred)
  315. if err != nil {
  316. log.Errorf("rsetAdminUser fail: %s", err)
  317. }
  318. }
  319. return nil
  320. }
  321. func (assign *SAssignment) getRole() (*SRole, error) {
  322. return RoleManager.FetchRoleById(assign.RoleId)
  323. }
  324. func (assign *SAssignment) getProject() (*SProject, error) {
  325. if assign.Type == api.AssignmentUserProject || assign.Type == api.AssignmentGroupProject {
  326. return ProjectManager.FetchProjectById(assign.TargetId)
  327. }
  328. return nil, nil
  329. }
  330. func (assign *SAssignment) getDomain() (*SDomain, error) {
  331. if assign.Type == api.AssignmentUserDomain || assign.Type == api.AssignmentGroupDomain {
  332. return DomainManager.FetchDomainById(assign.TargetId)
  333. }
  334. return nil, nil
  335. }
  336. func (manager *SAssignmentManager) batchRemove(ctx context.Context, userCred mcclient.TokenCredential, actorId string, typeStrs []string) error {
  337. q := manager.Query()
  338. q = q.In("type", typeStrs)
  339. q = q.Equals("actor_id", actorId)
  340. q = q.IsFalse("inherited")
  341. assigns := make([]SAssignment, 0)
  342. err := db.FetchModelObjects(manager, q, &assigns)
  343. if err != nil && err != sql.ErrNoRows {
  344. return errors.Wrap(err, "db.FetchModelObjects")
  345. }
  346. for i := range assigns {
  347. _, err := db.Update(&assigns[i], func() error {
  348. assigns[i].MarkDelete()
  349. return nil
  350. })
  351. if err != nil {
  352. return errors.Wrap(err, "db.Update")
  353. }
  354. // clear project admin Id
  355. role, _ := assigns[i].getRole()
  356. if role.Name == options.Options.ProjectAdminRole {
  357. project, _ := assigns[i].getProject()
  358. if project != nil && project.AdminId == actorId {
  359. err := project.resetAdminUser(ctx, userCred)
  360. if err != nil {
  361. log.Errorf("batchRemove project resetAdminUser fail %s", err)
  362. }
  363. }
  364. }
  365. }
  366. return nil
  367. }
  368. func (manager *SAssignmentManager) projectRemoveAllUser(ctx context.Context, userCred mcclient.TokenCredential, user *SUser) error {
  369. if user.IsAdminUser() {
  370. return httperrors.NewForbiddenError("sysadmin is protected")
  371. }
  372. // allow remove current user from current project. user takes the consequence
  373. // if user.Id == userCred.GetUserId() {
  374. // return httperrors.NewForbiddenError("cannot remove current user from current project")
  375. // }
  376. err := manager.batchRemove(ctx, userCred, user.Id, []string{api.AssignmentUserProject, api.AssignmentUserDomain})
  377. if err != nil {
  378. return errors.Wrap(err, "manager.batchRemove")
  379. }
  380. db.OpsLog.LogEvent(user, "leave_all_projects", user.GetShortDesc(ctx), userCred)
  381. return nil
  382. }
  383. func (manager *SAssignmentManager) projectRemoveAllGroup(ctx context.Context, userCred mcclient.TokenCredential, group *SGroup) error {
  384. err := manager.batchRemove(ctx, userCred, group.Id, []string{api.AssignmentGroupProject, api.AssignmentGroupDomain})
  385. if err != nil {
  386. return errors.Wrap(err, "manager.batchRemove")
  387. }
  388. db.OpsLog.LogEvent(group, "leave_all_projects", group.GetShortDesc(ctx), userCred)
  389. return nil
  390. }
  391. func (manager *SAssignmentManager) projectRemoveUser(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, user *SUser, role *SRole) error {
  392. if project.IsAdminProject() && user.IsAdminUser() && role.IsSystemRole() {
  393. return httperrors.NewForbiddenError("sysadmin is protected")
  394. }
  395. // allow remove current user from current project, user takes the consequence
  396. // prevent remove current user from current project
  397. // if project.Id == userCred.GetProjectId() && user.Id == userCred.GetUserId() {
  398. // return httperrors.NewForbiddenError("cannot remove current user from current project")
  399. // }
  400. if project.DomainId != user.DomainId {
  401. // if project.DomainId != api.DEFAULT_DOMAIN_ID {
  402. // return httperrors.NewInputParameterError("join user into project of default domain or identical domain")
  403. // } else
  404. if !db.IsAllowPerform(ctx, rbacscope.ScopeSystem, userCred, user, "leave-project") {
  405. return httperrors.NewForbiddenError("not enough privilege")
  406. }
  407. } else {
  408. if !db.IsAllowPerform(ctx, rbacscope.ScopeDomain, userCred, user, "leave-project") {
  409. return httperrors.NewForbiddenError("not enough privilege")
  410. }
  411. }
  412. err := manager.remove(api.AssignmentUserProject, user.Id, project.Id, role.Id)
  413. if err != nil {
  414. return errors.Wrap(err, "manager.remove")
  415. }
  416. db.OpsLog.LogEvent(user, db.ACT_DETACH, project.GetShortDesc(ctx), userCred)
  417. db.OpsLog.LogEvent(project, db.ACT_DETACH, user.GetShortDesc(ctx), userCred)
  418. if project.AdminId == user.Id && role.Name == options.Options.ProjectAdminRole {
  419. err := project.resetAdminUser(ctx, userCred)
  420. if err != nil {
  421. log.Errorf("resetAdminUser fail %s", err)
  422. }
  423. }
  424. return nil
  425. }
  426. func (manager *SAssignmentManager) projectAddGroup(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, group *SGroup, role *SRole) error {
  427. err := db.ValidateCreateDomainId(project.DomainId)
  428. if err != nil {
  429. return err
  430. }
  431. if project.DomainId != group.DomainId {
  432. // if project.DomainId != api.DEFAULT_DOMAIN_ID && !options.Options.AllowJoinProjectsAcrossDomains {
  433. // return httperrors.NewInputParameterError("join group into project of default domain or identical domain")
  434. // } else
  435. if !db.IsAllowPerform(ctx, rbacscope.ScopeSystem, userCred, group, "join-project") {
  436. return httperrors.NewForbiddenError("not enough privilege")
  437. }
  438. } else {
  439. if !db.IsAllowPerform(ctx, rbacscope.ScopeDomain, userCred, group, "join-project") {
  440. return httperrors.NewForbiddenError("not enough privilege")
  441. }
  442. }
  443. roleCnt, err := manager.fetchGroupProjectRoleCount(group.Id, project.Id)
  444. if err != nil {
  445. return errors.Wrap(err, "fetchGroupProjectRoleCount")
  446. }
  447. if roleCnt >= options.Options.MaxGroupRolesInProject {
  448. return errors.Wrapf(httperrors.ErrTooLarge, "group %s has joined project %s %d roles more than %d", group.Name, project.Name, roleCnt, options.Options.MaxGroupRolesInProject)
  449. }
  450. err = manager.add(ctx, api.AssignmentGroupProject, group.Id, project.Id, role.Id)
  451. if err != nil {
  452. return errors.Wrap(err, "manager.add")
  453. }
  454. db.OpsLog.LogEvent(group, db.ACT_ATTACH, project.GetShortDesc(ctx), userCred)
  455. db.OpsLog.LogEvent(project, db.ACT_ATTACH, group.GetShortDesc(ctx), userCred)
  456. if len(project.AdminId) == 0 && role.Name == options.Options.ProjectAdminRole {
  457. err := project.resetAdminUser(ctx, userCred)
  458. if err != nil {
  459. log.Errorf("rsetAdminUser fail: %s", err)
  460. }
  461. }
  462. return nil
  463. }
  464. func (manager *SAssignmentManager) projectRemoveGroup(ctx context.Context, userCred mcclient.TokenCredential, project *SProject, group *SGroup, role *SRole) error {
  465. if project.DomainId != group.DomainId {
  466. // if project.DomainId != api.DEFAULT_DOMAIN_ID {
  467. // return httperrors.NewInputParameterError("join group into project of default domain or identical domain")
  468. // } else
  469. if !db.IsAllowPerform(ctx, rbacscope.ScopeSystem, userCred, group, "leave-project") {
  470. return httperrors.NewForbiddenError("not enough privilege")
  471. }
  472. } else {
  473. if !db.IsAllowPerform(ctx, rbacscope.ScopeDomain, userCred, group, "leave-project") {
  474. return httperrors.NewForbiddenError("not enough privilege")
  475. }
  476. }
  477. err := manager.remove(api.AssignmentGroupProject, group.Id, project.Id, role.Id)
  478. if err != nil {
  479. return errors.Wrap(err, "manager.remove")
  480. }
  481. db.OpsLog.LogEvent(group, db.ACT_DETACH, project.GetShortDesc(ctx), userCred)
  482. db.OpsLog.LogEvent(project, db.ACT_DETACH, group.GetShortDesc(ctx), userCred)
  483. if len(project.AdminId) > 0 && role.Name == options.Options.ProjectAdminRole {
  484. err := project.resetAdminUser(ctx, userCred)
  485. if err != nil {
  486. log.Errorf("rsetAdminUser fail: %s", err)
  487. }
  488. }
  489. return nil
  490. }
  491. func (manager *SAssignmentManager) remove(typeStr, actorId, projectId, roleId string) error {
  492. assign := SAssignment{
  493. Type: typeStr,
  494. ActorId: actorId,
  495. TargetId: projectId,
  496. RoleId: roleId,
  497. Inherited: tristate.False,
  498. }
  499. assign.SetModelManager(manager, &assign)
  500. _, err := db.Update(&assign, func() error {
  501. return assign.MarkDelete()
  502. })
  503. if err != nil && err != sql.ErrNoRows {
  504. return err
  505. }
  506. return nil
  507. }
  508. func (manager *SAssignmentManager) add(ctx context.Context, typeStr, actorId, projectId, roleId string) error {
  509. assign := SAssignment{
  510. Type: typeStr,
  511. ActorId: actorId,
  512. TargetId: projectId,
  513. RoleId: roleId,
  514. Inherited: tristate.False,
  515. }
  516. assign.SetModelManager(manager, &assign)
  517. err := manager.TableSpec().InsertOrUpdate(ctx, &assign)
  518. if err != nil {
  519. return errors.Wrap(err, "InsertOrUpdate")
  520. }
  521. return nil
  522. }
  523. func AddAdhocHandlers(version string, app *appsrv.Application) {
  524. app.AddHandler2("GET", fmt.Sprintf("%s/role_assignments", version), auth.Authenticate(roleAssignmentHandler), nil, "list_role_assignments", nil)
  525. }
  526. func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
  527. _, query, _ := appsrv.FetchEnv(ctx, w, r)
  528. input := api.RoleAssignmentsInput{}
  529. err := query.Unmarshal(&input)
  530. if err != nil {
  531. httperrors.GeneralServerError(ctx, w, err)
  532. return
  533. }
  534. includeNames := (input.IncludeNames != nil)
  535. effective := (input.Effective != nil)
  536. includeSub := (input.IncludeSubtree != nil)
  537. includeSystem := (input.IncludeSystem != nil)
  538. includePolicies := (input.IncludePolicies != nil)
  539. limit := 0
  540. if input.Limit != nil {
  541. limit = *input.Limit
  542. }
  543. offset := 0
  544. if input.Offset != nil {
  545. offset = *input.Offset
  546. }
  547. results, total, err := AssignmentManager.FetchAll(
  548. input.User.Id,
  549. input.Group.Id,
  550. input.Role.Id,
  551. input.Scope.Domain.Id,
  552. input.Scope.Project.Id,
  553. input.ProjectDomainId,
  554. input.Users,
  555. input.Groups,
  556. input.Roles,
  557. input.Domains,
  558. input.Projects,
  559. input.ProjectDomains,
  560. includeNames, effective, includeSub, includeSystem, includePolicies,
  561. limit, offset)
  562. if err != nil {
  563. httperrors.GeneralServerError(ctx, w, err)
  564. return
  565. }
  566. output := api.RoleAssignmentsOutput{}
  567. output.RoleAssignments = results
  568. output.Total = total
  569. output.Limit = limit
  570. output.Offset = offset
  571. appsrv.SendJSON(w, jsonutils.Marshal(output))
  572. }
  573. func (manager *SAssignmentManager) queryAll(
  574. userId, groupId, roleId, domainId, projectId string, projectDomainId string,
  575. users, groups, roles, domains, projects, projectDomains []string,
  576. ) *sqlchemy.SQuery {
  577. assigments := manager.Query().SubQuery()
  578. q := assigments.Query(
  579. assigments.Field("type"),
  580. sqlchemy.NewFunction(
  581. sqlchemy.NewCase().When(sqlchemy.OR(
  582. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentUserProject)),
  583. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentUserDomain)),
  584. ), assigments.Field("actor_id")).Else(sqlchemy.NewStringField("")),
  585. "user_id",
  586. false,
  587. ),
  588. sqlchemy.NewFunction(
  589. sqlchemy.NewCase().When(sqlchemy.OR(
  590. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentGroupProject)),
  591. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentGroupDomain)),
  592. ), assigments.Field("actor_id")).Else(sqlchemy.NewStringField("")),
  593. "group_id",
  594. false,
  595. ),
  596. sqlchemy.NewFunction(
  597. sqlchemy.NewCase().When(sqlchemy.OR(
  598. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentUserDomain)),
  599. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentGroupDomain)),
  600. ), assigments.Field("target_id")).Else(sqlchemy.NewStringField("")),
  601. "domain_id",
  602. false,
  603. ),
  604. sqlchemy.NewFunction(
  605. sqlchemy.NewCase().When(sqlchemy.OR(
  606. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentUserProject)),
  607. sqlchemy.Equals(assigments.Field("type"), sqlchemy.NewStringField(api.AssignmentGroupProject)),
  608. ), assigments.Field("target_id")).Else(sqlchemy.NewStringField("")),
  609. "project_id",
  610. false,
  611. ),
  612. assigments.Field("role_id"),
  613. )
  614. // here use subquery.query to produce a effective reference to case function fields
  615. q = q.SubQuery().Query()
  616. if len(userId) > 0 {
  617. q = q.In("type", []string{api.AssignmentUserProject, api.AssignmentUserDomain}).Equals("user_id", userId)
  618. }
  619. if len(users) > 0 {
  620. subq := UserManager.Query("id")
  621. subq = subq.Filter(sqlchemy.OR(
  622. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(users)),
  623. sqlchemy.ContainsAny(subq.Field("name"), users),
  624. ))
  625. q = q.In("type", []string{api.AssignmentUserProject, api.AssignmentUserDomain}).In("user_id", subq.SubQuery())
  626. }
  627. if len(groupId) > 0 {
  628. q = q.In("type", []string{api.AssignmentGroupProject, api.AssignmentGroupDomain}).Equals("group_id", groupId)
  629. }
  630. if len(groups) > 0 {
  631. subq := GroupManager.Query("id")
  632. subq = subq.Filter(sqlchemy.OR(
  633. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(groups)),
  634. sqlchemy.ContainsAny(subq.Field("name"), groups),
  635. ))
  636. q = q.In("type", []string{api.AssignmentGroupProject, api.AssignmentGroupDomain}).In("group_id", subq.SubQuery())
  637. }
  638. if len(roleId) > 0 {
  639. q = q.Equals("role_id", roleId)
  640. }
  641. if len(roles) > 0 {
  642. subq := RoleManager.Query("id")
  643. subq = subq.Filter(sqlchemy.OR(
  644. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(roles)),
  645. sqlchemy.ContainsAny(subq.Field("name"), roles),
  646. ))
  647. q = q.In("role_id", subq.SubQuery())
  648. }
  649. if len(projectId) > 0 {
  650. q = q.Equals("project_id", projectId).In("type", []string{api.AssignmentUserProject, api.AssignmentGroupProject})
  651. }
  652. if len(projects) > 0 {
  653. subq := ProjectManager.Query("id")
  654. subq = subq.Filter(sqlchemy.OR(
  655. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(projects)),
  656. sqlchemy.ContainsAny(subq.Field("name"), projects),
  657. ))
  658. q = q.In("project_id", subq.SubQuery()).In("type", []string{api.AssignmentUserProject, api.AssignmentGroupProject})
  659. }
  660. if len(projectDomainId) > 0 {
  661. subq := ProjectManager.Query("id").Equals("domain_id", projectDomainId)
  662. q = q.In("project_id", subq.SubQuery()).In("type", []string{api.AssignmentUserProject, api.AssignmentGroupProject})
  663. }
  664. if len(projectDomains) > 0 {
  665. subq := ProjectManager.Query("id")
  666. domainQ := DomainManager.Query("id", "name").SubQuery()
  667. subq = subq.Join(domainQ, sqlchemy.Equals(subq.Field("domain_id"), domainQ.Field("id")))
  668. subq = subq.Filter(sqlchemy.OR(
  669. sqlchemy.In(domainQ.Field("id"), stringutils2.RemoveUtf8Strings(projectDomains)),
  670. sqlchemy.ContainsAny(domainQ.Field("name"), projectDomains),
  671. ))
  672. q = q.In("project_id", subq.SubQuery()).In("type", []string{api.AssignmentUserProject, api.AssignmentGroupProject})
  673. }
  674. if len(domainId) > 0 {
  675. q = q.Equals("domain_id", domainId).In("type", []string{api.AssignmentUserDomain, api.AssignmentGroupDomain})
  676. }
  677. if len(domains) > 0 {
  678. subq := DomainManager.Query("id")
  679. subq = subq.Filter(sqlchemy.OR(
  680. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(domains)),
  681. sqlchemy.ContainsAny(subq.Field("name"), domains),
  682. ))
  683. q = q.In("domain_id", subq.SubQuery()).In("type", []string{api.AssignmentUserDomain, api.AssignmentGroupDomain})
  684. }
  685. return q
  686. }
  687. func fetchRoleAssignmentPolicies(ra *api.SRoleAssignment) {
  688. policyNames, _, _ := RolePolicyManager.GetMatchPolicyGroup(ra, time.Time{}, true)
  689. ra.Policies.Project, _ = policyNames[rbacscope.ScopeProject]
  690. ra.Policies.Domain, _ = policyNames[rbacscope.ScopeDomain]
  691. ra.Policies.System, _ = policyNames[rbacscope.ScopeSystem]
  692. }
  693. type sAssignmentInternal struct {
  694. Type string `json:"type"`
  695. UserId string `json:"user_id"`
  696. GroupId string `json:"group_id"`
  697. DomainId string `json:"domain_id"`
  698. ProjectId string `json:"project_id"`
  699. RoleId string `json:"role_id"`
  700. }
  701. func (assign *sAssignmentInternal) getRoleAssignment(domains, projects, groups, users, roles map[string]api.SFetchDomainObject, fetchPolicies bool, projectMetadata map[string]map[string]string) api.SRoleAssignment {
  702. ra := api.SRoleAssignment{}
  703. ra.Role.Id = assign.RoleId
  704. ra.Role.Name = roles[assign.RoleId].Name
  705. ra.Role.Domain.Id = roles[assign.RoleId].DomainId
  706. ra.Role.Domain.Name = roles[assign.RoleId].Domain
  707. if len(assign.UserId) > 0 {
  708. ra.User.Id = assign.UserId
  709. ra.User.Name = users[assign.UserId].Name
  710. ra.User.Domain.Id = users[assign.UserId].DomainId
  711. ra.User.Domain.Name = users[assign.UserId].Domain
  712. }
  713. if len(assign.GroupId) > 0 {
  714. ra.Group.Id = assign.GroupId
  715. ra.Group.Name = groups[assign.GroupId].Name
  716. ra.Group.Domain.Id = groups[assign.GroupId].DomainId
  717. ra.Group.Domain.Name = groups[assign.GroupId].Domain
  718. }
  719. if len(assign.ProjectId) > 0 {
  720. ra.Scope.Project.Id = assign.ProjectId
  721. ra.Scope.Project.Name = projects[assign.ProjectId].Name
  722. ra.Scope.Project.Metadata, _ = projectMetadata[assign.ProjectId]
  723. ra.Scope.Project.Domain.Id = projects[assign.ProjectId].DomainId
  724. ra.Scope.Project.Domain.Name = projects[assign.ProjectId].Domain
  725. if fetchPolicies {
  726. fetchRoleAssignmentPolicies(&ra)
  727. }
  728. } else if len(assign.DomainId) > 0 {
  729. ra.Scope.Domain.Id = assign.DomainId
  730. ra.Scope.Domain.Name = domains[assign.DomainId].Name
  731. }
  732. return ra
  733. }
  734. func (manager *SAssignmentManager) FetchAll(
  735. userId, groupId, roleId, domainId, projectId string, projectDomainId string,
  736. userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs []string,
  737. includeNames, effective, includeSub, includeSystem, includePolicies bool,
  738. limit, offset int) ([]api.SRoleAssignment, int64, error) {
  739. var q *sqlchemy.SQuery
  740. if effective {
  741. usrq := manager.queryAll(userId, "", roleId, domainId, projectId, projectDomainId, userStrs, nil, roleStrs, domainStrs, projectStrs, projectDomainStrs).In("type", []string{api.AssignmentUserProject, api.AssignmentUserDomain})
  742. memberships := UsergroupManager.Query("user_id", "group_id").SubQuery()
  743. grpproj := manager.queryAll("", groupId, roleId, domainId, projectId, projectDomainId, nil, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs).In("type", []string{api.AssignmentGroupProject, api.AssignmentGroupDomain}).SubQuery()
  744. q2 := grpproj.Query(
  745. grpproj.Field("type"),
  746. memberships.Field("user_id"),
  747. grpproj.Field("group_id"),
  748. grpproj.Field("domain_id"),
  749. grpproj.Field("project_id"),
  750. grpproj.Field("role_id"),
  751. )
  752. q2 = q2.LeftJoin(memberships, sqlchemy.Equals(grpproj.Field("group_id"), memberships.Field("group_id")))
  753. if len(userId) > 0 {
  754. q2 = q2.Filter(sqlchemy.Equals(memberships.Field("user_id"), userId))
  755. }
  756. if len(userStrs) > 0 {
  757. subq := UserManager.Query("id")
  758. subq = subq.Filter(sqlchemy.OR(
  759. sqlchemy.In(subq.Field("id"), stringutils2.RemoveUtf8Strings(userStrs)),
  760. sqlchemy.ContainsAny(subq.Field("name"), userStrs),
  761. ))
  762. q2 = q2.Filter(sqlchemy.In(memberships.Field("user_id"), subq.SubQuery()))
  763. }
  764. q = sqlchemy.Union(usrq, q2).Query().Distinct()
  765. } else {
  766. q = manager.queryAll(userId, groupId, roleId, domainId, projectId, projectDomainId, userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs).Distinct()
  767. }
  768. if !includeSystem {
  769. users := UserManager.Query().SubQuery()
  770. q = q.LeftJoin(users, sqlchemy.Equals(q.Field("user_id"), users.Field("id")))
  771. q = q.Filter(sqlchemy.OR(
  772. sqlchemy.IsFalse(users.Field("is_system_account")),
  773. sqlchemy.IsNull(users.Field("is_system_account")),
  774. ))
  775. }
  776. total, err := q.CountWithError()
  777. if err != nil {
  778. return nil, -1, errors.Wrap(err, "q.Count")
  779. }
  780. if limit > 0 {
  781. q = q.Limit(limit)
  782. }
  783. if offset > 0 {
  784. q = q.Offset(offset)
  785. }
  786. assigns := make([]sAssignmentInternal, 0)
  787. err = q.All(&assigns)
  788. if err != nil && err != sql.ErrNoRows {
  789. return nil, -1, httperrors.NewInternalServerError("query error %s", err)
  790. }
  791. domainIds := stringutils2.SSortedStrings{}
  792. projectIds := stringutils2.SSortedStrings{}
  793. groupIds := stringutils2.SSortedStrings{}
  794. userIds := stringutils2.SSortedStrings{}
  795. roleIds := stringutils2.SSortedStrings{}
  796. for i := range assigns {
  797. if len(assigns[i].UserId) > 0 {
  798. userIds = stringutils2.Append(userIds, assigns[i].UserId)
  799. }
  800. if len(assigns[i].GroupId) > 0 {
  801. groupIds = stringutils2.Append(groupIds, assigns[i].GroupId)
  802. }
  803. if len(assigns[i].DomainId) > 0 {
  804. domainIds = stringutils2.Append(domainIds, assigns[i].DomainId)
  805. }
  806. if len(assigns[i].ProjectId) > 0 {
  807. projectIds = stringutils2.Append(projectIds, assigns[i].ProjectId)
  808. }
  809. roleIds = stringutils2.Append(roleIds, assigns[i].RoleId)
  810. }
  811. domains, err := fetchObjects(DomainManager, domainIds)
  812. if err != nil {
  813. return nil, -1, errors.Wrap(err, "fetchObjects DomainManager")
  814. }
  815. projects, err := fetchObjects(ProjectManager, projectIds)
  816. if err != nil {
  817. return nil, -1, errors.Wrap(err, "fetchObjects ProjectManager")
  818. }
  819. projectMetadatas := fetchProjectMetadatas(projectIds)
  820. groups, err := fetchObjects(GroupManager, groupIds)
  821. if err != nil {
  822. return nil, -1, errors.Wrap(err, "fetchObjects GroupManager")
  823. }
  824. users, err := fetchObjects(UserManager, userIds)
  825. if err != nil {
  826. return nil, -1, errors.Wrap(err, "fetchObjects UserManager")
  827. }
  828. roles, err := fetchObjects(RoleManager, roleIds)
  829. if err != nil {
  830. return nil, -1, errors.Wrap(err, "fetchObjects RoleManager")
  831. }
  832. results := make([]api.SRoleAssignment, len(assigns))
  833. for i := range assigns {
  834. results[i] = assigns[i].getRoleAssignment(domains, projects, groups, users, roles, includePolicies, projectMetadatas)
  835. }
  836. return results, int64(total), nil
  837. }
  838. func (manager *SAssignmentManager) isUserInProjectWithRole(userId, projectId, roleId string) (bool, error) {
  839. q := manager.fetchUserProjectRoleIdsQuery(userId, projectId)
  840. q = q.Equals("role_id", roleId)
  841. cnt, err := q.CountWithError()
  842. if err != nil {
  843. return false, errors.Wrap(err, "CountWithError")
  844. }
  845. if cnt > 0 {
  846. return true, nil
  847. } else {
  848. return false, nil
  849. }
  850. }
  851. func fetchProjectMetadatas(idList []string) map[string]map[string]string {
  852. ret := map[string]map[string]string{}
  853. if len(idList) == 0 {
  854. return ret
  855. }
  856. q := db.Metadata.Query().Equals("obj_type", "project").In("obj_id", idList)
  857. result := []db.SMetadata{}
  858. err := q.All(&result)
  859. if err != nil {
  860. return ret
  861. }
  862. for i := range result {
  863. _, ok := ret[result[i].ObjId]
  864. if !ok {
  865. ret[result[i].ObjId] = map[string]string{}
  866. }
  867. ret[result[i].ObjId][result[i].Key] = result[i].Value
  868. }
  869. return ret
  870. }
  871. func fetchObjects(manager db.IModelManager, idList []string) (map[string]api.SFetchDomainObject, error) {
  872. results := make(map[string]api.SFetchDomainObject)
  873. if len(idList) == 0 {
  874. return results, nil
  875. }
  876. var q *sqlchemy.SQuery
  877. if manager == DomainManager {
  878. q = DomainManager.Query().In("id", idList)
  879. } else {
  880. resq := manager.Query().SubQuery()
  881. domains := DomainManager.Query().SubQuery()
  882. q = resq.Query(resq.Field("id"), resq.Field("name"), resq.Field("domain_id"), domains.Field("name", "domain"))
  883. q = q.Join(domains, sqlchemy.Equals(domains.Field("id"), resq.Field("domain_id")))
  884. q = q.Filter(sqlchemy.IsTrue(domains.Field("is_domain")))
  885. q = q.Filter(sqlchemy.In(resq.Field("id"), idList))
  886. }
  887. objs := make([]api.SFetchDomainObject, 0)
  888. err := q.All(&objs)
  889. if err != nil && err != sql.ErrNoRows {
  890. return nil, errors.Wrap(err, "query")
  891. }
  892. for i := range objs {
  893. results[objs[i].Id] = objs[i]
  894. }
  895. return results, nil
  896. }