projects.go 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  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. "strings"
  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/pinyinutils"
  26. "yunion.io/x/pkg/util/rbacscope"
  27. "yunion.io/x/sqlchemy"
  28. api "yunion.io/x/onecloud/pkg/apis/identity"
  29. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  30. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
  34. "yunion.io/x/onecloud/pkg/httperrors"
  35. "yunion.io/x/onecloud/pkg/keystone/options"
  36. "yunion.io/x/onecloud/pkg/mcclient"
  37. "yunion.io/x/onecloud/pkg/util/logclient"
  38. "yunion.io/x/onecloud/pkg/util/rbacutils"
  39. "yunion.io/x/onecloud/pkg/util/stringutils2"
  40. "yunion.io/x/onecloud/pkg/util/tagutils"
  41. )
  42. type SProjectManager struct {
  43. SIdentityBaseResourceManager
  44. }
  45. var ProjectManager *SProjectManager
  46. func init() {
  47. ProjectManager = &SProjectManager{
  48. SIdentityBaseResourceManager: NewIdentityBaseResourceManager(
  49. SProject{},
  50. "project",
  51. "project",
  52. "projects",
  53. ),
  54. }
  55. ProjectManager.SetVirtualObject(ProjectManager)
  56. notifyclient.AddNotifyDBHookResources(ProjectManager.KeywordPlural(), ProjectManager.AliasPlural())
  57. }
  58. /*
  59. +-------------+-------------+------+-----+---------+-------+
  60. | Field | Type | Null | Key | Default | Extra |
  61. +-------------+-------------+------+-----+---------+-------+
  62. | id | varchar(64) | NO | PRI | NULL | |
  63. | name | varchar(64) | NO | | NULL | |
  64. | extra | text | YES | | NULL | |
  65. | description | text | YES | | NULL | |
  66. | enabled | tinyint(1) | YES | | NULL | |
  67. | domain_id | varchar(64) | NO | MUL | NULL | |
  68. | parent_id | varchar(64) | YES | MUL | NULL | |
  69. | is_domain | tinyint(1) | NO | | 0 | |
  70. | created_at | datetime | YES | | NULL | |
  71. +-------------+-------------+------+-----+---------+-------+
  72. */
  73. type SProject struct {
  74. SIdentityBaseResource
  75. // 上级项目或域的ID
  76. ParentId string `width:"64" charset:"ascii" list:"domain" create:"domain_optional"`
  77. // 该项目是否为域(domain)
  78. IsDomain tristate.TriState `default:"false"`
  79. AdminId string `width:"64" charset:"ascii" nullable:"true" list:"domain"`
  80. }
  81. func (manager *SProjectManager) GetContextManagers() [][]db.IModelManager {
  82. return [][]db.IModelManager{
  83. {UserManager},
  84. {GroupManager},
  85. }
  86. }
  87. func (manager *SProjectManager) InitializeData() error {
  88. ctx := context.TODO()
  89. err := manager.initSysProject(ctx)
  90. if err != nil {
  91. return errors.Wrap(err, "initSysProject")
  92. }
  93. return nil
  94. }
  95. func (manager *SProjectManager) initSysProject(ctx context.Context) error {
  96. q := manager.Query().Equals("name", api.SystemAdminProject)
  97. q = q.Equals("domain_id", api.DEFAULT_DOMAIN_ID)
  98. cnt, err := q.CountWithError()
  99. if err != nil {
  100. return errors.Wrap(err, "query")
  101. }
  102. if cnt == 1 {
  103. return nil
  104. }
  105. if cnt > 2 {
  106. // ???
  107. log.Fatalf("duplicate system project???")
  108. }
  109. // insert
  110. project := SProject{}
  111. project.Name = api.SystemAdminProject
  112. project.DomainId = api.DEFAULT_DOMAIN_ID
  113. // project.Enabled = tristate.True
  114. project.Description = "Boostrap system default admin project"
  115. project.IsDomain = tristate.False
  116. project.ParentId = api.DEFAULT_DOMAIN_ID
  117. project.SetModelManager(manager, &project)
  118. err = manager.TableSpec().Insert(ctx, &project)
  119. if err != nil {
  120. return errors.Wrap(err, "insert")
  121. }
  122. return nil
  123. }
  124. func (project *SProject) resetAdminUser(ctx context.Context, userCred mcclient.TokenCredential) error {
  125. role, err := RoleManager.FetchRoleByName(options.Options.ProjectAdminRole, "", "")
  126. if err != nil {
  127. return errors.Wrapf(err, "FetchRoleByName %s", options.Options.ProjectAdminRole)
  128. }
  129. q := AssignmentManager.fetchProjectRoleUserIdsQuery(project.Id, role.Id)
  130. userId := struct {
  131. ActorId string
  132. }{}
  133. err = q.First(&userId)
  134. if err != nil && errors.Cause(err) != sql.ErrNoRows {
  135. return errors.Wrap(err, "query")
  136. }
  137. err = project.setAdminId(ctx, userCred, userId.ActorId)
  138. if err != nil {
  139. return errors.Wrap(err, "setAdminId")
  140. }
  141. return nil
  142. }
  143. func (manager *SProjectManager) NewQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, useRawQuery bool) *sqlchemy.SQuery {
  144. return manager.Query()
  145. }
  146. func (manager *SProjectManager) Query(fields ...string) *sqlchemy.SQuery {
  147. return manager.SIdentityBaseResourceManager.Query(fields...).IsFalse("is_domain")
  148. }
  149. func (manager *SProjectManager) FetchProjectByName(projectName string, domainId, domainName string) (*SProject, error) {
  150. obj, err := db.NewModelObject(manager)
  151. if err != nil {
  152. return nil, errors.Wrap(err, "db.NewModelObject")
  153. }
  154. if len(domainId) == 0 && len(domainName) == 0 {
  155. q := manager.Query().Equals("name", projectName)
  156. cnt, err := q.CountWithError()
  157. if err != nil {
  158. return nil, errors.Wrap(err, "CountWithError")
  159. }
  160. if cnt == 0 {
  161. return nil, sql.ErrNoRows
  162. }
  163. if cnt > 1 {
  164. return nil, sqlchemy.ErrDuplicateEntry
  165. }
  166. err = q.First(obj)
  167. if err != nil {
  168. return nil, errors.Wrap(err, "q.First")
  169. }
  170. } else {
  171. domain, err := DomainManager.FetchDomain(domainId, domainName)
  172. if err != nil {
  173. return nil, errors.Wrap(err, "DomainManager.FetchDomain")
  174. }
  175. q := manager.Query().Equals("name", projectName).Equals("domain_id", domain.Id)
  176. err = q.First(obj)
  177. if err != nil {
  178. return nil, errors.Wrap(err, "q.First")
  179. }
  180. }
  181. return obj.(*SProject), nil
  182. }
  183. func (manager *SProjectManager) FetchProjectById(projectId string) (*SProject, error) {
  184. obj, err := db.NewModelObject(manager)
  185. if err != nil {
  186. return nil, err
  187. }
  188. q := manager.Query().Equals("id", projectId)
  189. err = q.First(obj)
  190. if err != nil {
  191. return nil, err
  192. }
  193. return obj.(*SProject), err
  194. }
  195. func (manager *SProjectManager) FetchProject(projectId, projectName string, domainId, domainName string) (*SProject, error) {
  196. if len(projectId) > 0 {
  197. return manager.FetchProjectById(projectId)
  198. }
  199. if len(projectName) > 0 {
  200. return manager.FetchProjectByName(projectName, domainId, domainName)
  201. }
  202. return nil, fmt.Errorf("no project Id or name provided")
  203. }
  204. // +onecloud:model-api-gen
  205. type SProjectExtended struct {
  206. SProject
  207. DomainName string
  208. }
  209. func (proj *SProject) getDomain() (*SDomain, error) {
  210. return DomainManager.FetchDomainById(proj.DomainId)
  211. }
  212. func (proj *SProject) FetchExtend() (*SProjectExtended, error) {
  213. domain, err := proj.getDomain()
  214. if err != nil {
  215. return nil, err
  216. }
  217. ext := SProjectExtended{
  218. SProject: *proj,
  219. DomainName: domain.Name,
  220. }
  221. return &ext, nil
  222. }
  223. // 项目列表
  224. func (manager *SProjectManager) ListItemFilter(
  225. ctx context.Context,
  226. q *sqlchemy.SQuery,
  227. userCred mcclient.TokenCredential,
  228. query api.ProjectListInput,
  229. ) (*sqlchemy.SQuery, error) {
  230. var err error
  231. q, err = manager.SIdentityBaseResourceManager.ListItemFilter(ctx, q, userCred, query.IdentityBaseResourceListInput)
  232. if err != nil {
  233. return nil, errors.Wrap(err, "SIdentityBaseResourceManager.ListItemFilter")
  234. }
  235. if !query.PolicyProjectTags.IsEmpty() {
  236. policyFilters := tagutils.STagFilters{}
  237. policyFilters.AddFilters(query.PolicyProjectTags)
  238. q = db.ObjectIdQueryWithTagFilters(ctx, q, "id", "project", policyFilters)
  239. }
  240. userStr := query.UserId
  241. if len(userStr) > 0 {
  242. userObj, err := UserManager.FetchById(userStr)
  243. if err != nil {
  244. if err == sql.ErrNoRows {
  245. return nil, httperrors.NewResourceNotFoundError2(UserManager.Keyword(), userStr)
  246. } else {
  247. return nil, httperrors.NewGeneralError(err)
  248. }
  249. }
  250. subq := AssignmentManager.fetchUserProjectIdsQuery(userObj.GetId())
  251. if query.Jointable != nil && *query.Jointable {
  252. user := userObj.(*SUser)
  253. if user.DomainId == api.DEFAULT_DOMAIN_ID {
  254. q = q.Equals("domain_id", api.DEFAULT_DOMAIN_ID)
  255. } else {
  256. q = q.In("domain_id", []string{user.DomainId, api.DEFAULT_DOMAIN_ID})
  257. }
  258. q = q.NotIn("id", subq.SubQuery())
  259. } else {
  260. q = q.In("id", subq.SubQuery())
  261. }
  262. }
  263. if len(query.AdminId) > 0 {
  264. sq := UserManager.Query("id")
  265. sq = sq.Filter(
  266. sqlchemy.OR(
  267. sqlchemy.In(sq.Field("id"), query.AdminId),
  268. sqlchemy.In(sq.Field("name"), query.AdminId),
  269. ),
  270. )
  271. q = q.In("admin_id", sq.SubQuery())
  272. }
  273. groupStr := query.GroupId
  274. if len(groupStr) > 0 {
  275. groupObj, err := GroupManager.FetchById(groupStr)
  276. if err != nil {
  277. if err == sql.ErrNoRows {
  278. return nil, httperrors.NewResourceNotFoundError2(GroupManager.Keyword(), groupStr)
  279. } else {
  280. return nil, httperrors.NewGeneralError(err)
  281. }
  282. }
  283. subq := AssignmentManager.fetchGroupProjectIdsQuery(groupObj.GetId())
  284. if query.Jointable != nil && *query.Jointable {
  285. group := groupObj.(*SGroup)
  286. if group.DomainId == api.DEFAULT_DOMAIN_ID {
  287. q = q.Equals("domain_id", api.DEFAULT_DOMAIN_ID)
  288. } else {
  289. q = q.In("domain_id", []string{group.DomainId, api.DEFAULT_DOMAIN_ID})
  290. }
  291. q = q.NotIn("id", subq.SubQuery())
  292. } else {
  293. q = q.In("id", subq.SubQuery())
  294. }
  295. }
  296. if len(query.IdpId) > 0 {
  297. idpObj, err := IdentityProviderManager.FetchByIdOrName(ctx, userCred, query.IdpId)
  298. if err != nil {
  299. if errors.Cause(err) == sql.ErrNoRows {
  300. return nil, httperrors.NewResourceNotFoundError2(IdentityProviderManager.Keyword(), query.IdpId)
  301. } else {
  302. return nil, errors.Wrap(err, "IdentityProviderManager.FetchByIdOrName")
  303. }
  304. }
  305. subq := IdmappingManager.FetchPublicIdsExcludesQuery(idpObj.GetId(), api.IdMappingEntityDomain, nil)
  306. q = q.In("domain_id", subq.SubQuery())
  307. }
  308. return q, nil
  309. }
  310. func (manager *SProjectManager) OrderByExtraFields(
  311. ctx context.Context,
  312. q *sqlchemy.SQuery,
  313. userCred mcclient.TokenCredential,
  314. query api.ProjectListInput,
  315. ) (*sqlchemy.SQuery, error) {
  316. var err error
  317. q, err = manager.SIdentityBaseResourceManager.OrderByExtraFields(ctx, q, userCred, query.IdentityBaseResourceListInput)
  318. if err != nil {
  319. return nil, errors.Wrap(err, "SIdentityBaseResourceManager.OrderByExtraFields")
  320. }
  321. return q, nil
  322. }
  323. func (manager *SProjectManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  324. var err error
  325. q, err = manager.SIdentityBaseResourceManager.QueryDistinctExtraField(q, field)
  326. if err == nil {
  327. return q, nil
  328. }
  329. if field == "admin" {
  330. userQuery := UserManager.Query("name", "id").Distinct().SubQuery()
  331. q.AppendField(userQuery.Field("name", field))
  332. q = q.Join(userQuery, sqlchemy.Equals(q.Field("admin_id"), userQuery.Field("id")))
  333. q.GroupBy(userQuery.Field("name"))
  334. return q, nil
  335. }
  336. return q, httperrors.ErrNotFound
  337. }
  338. func (model *SProject) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  339. model.ParentId = ownerId.GetProjectDomainId()
  340. model.IsDomain = tristate.False
  341. return model.SIdentityBaseResource.CustomizeCreate(ctx, userCred, ownerId, query, data)
  342. }
  343. func (proj *SProject) GetUserCount() (int, error) {
  344. q := AssignmentManager.fetchProjectUserIdsQuery(proj.Id)
  345. return q.CountWithError()
  346. }
  347. func (proj *SProject) GetGroupCount() (int, error) {
  348. q := AssignmentManager.fetchProjectGroupIdsQuery(proj.Id)
  349. return q.CountWithError()
  350. }
  351. func (proj *SProject) ValidateDeleteCondition(ctx context.Context, info *api.ProjectDetails) error {
  352. if proj.IsAdminProject() {
  353. return httperrors.NewForbiddenError("cannot delete system project")
  354. }
  355. /*if len(info.ExtResource) > 0 {
  356. return httperrors.NewNotEmptyError("project contains external resources")
  357. }*/
  358. if info.UserCount > 0 {
  359. return httperrors.NewNotEmptyError("project contains user")
  360. }
  361. if info.GroupCount > 0 {
  362. return httperrors.NewNotEmptyError("project contains group")
  363. }
  364. return proj.SIdentityBaseResource.ValidateDeleteCondition(ctx, nil)
  365. }
  366. func (proj *SProject) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
  367. err := proj.SIdentityBaseResource.Delete(ctx, userCred)
  368. if err != nil {
  369. return errors.Wrap(err, "project delete")
  370. }
  371. notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
  372. Obj: proj,
  373. Action: notifyclient.ActionDelete,
  374. })
  375. return nil
  376. }
  377. func (proj *SProject) IsAdminProject() bool {
  378. return proj.Name == api.SystemAdminProject && proj.DomainId == api.DEFAULT_DOMAIN_ID
  379. }
  380. func (proj *SProject) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ProjectUpdateInput) (api.ProjectUpdateInput, error) {
  381. if len(input.Name) > 0 {
  382. if proj.IsAdminProject() {
  383. return input, httperrors.NewForbiddenError("cannot alter system project name")
  384. }
  385. }
  386. var err error
  387. input.IdentityBaseUpdateInput, err = proj.SIdentityBaseResource.ValidateUpdateData(ctx, userCred, query, input.IdentityBaseUpdateInput)
  388. if err != nil {
  389. return input, errors.Wrap(err, "SIdentityBaseResource.ValidateUpdateData")
  390. }
  391. return input, nil
  392. }
  393. func (manager *SProjectManager) FetchCustomizeColumns(
  394. ctx context.Context,
  395. userCred mcclient.TokenCredential,
  396. query jsonutils.JSONObject,
  397. objs []interface{},
  398. fields stringutils2.SSortedStrings,
  399. isList bool,
  400. ) []api.ProjectDetails {
  401. rows := make([]api.ProjectDetails, len(objs))
  402. identRows := manager.SIdentityBaseResourceManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  403. projIds := make([]string, len(objs))
  404. adminUserIds := make([]string, 0)
  405. for i := range rows {
  406. rows[i] = api.ProjectDetails{
  407. IdentityBaseResourceDetails: identRows[i],
  408. }
  409. proj := objs[i].(*SProject)
  410. projIds[i] = proj.Id
  411. if len(proj.AdminId) > 0 {
  412. adminUserIds = append(adminUserIds, proj.AdminId)
  413. }
  414. }
  415. extResource, extLastUpdate, err := ScopeResourceManager.FetchProjectsScopeResources(projIds)
  416. if err != nil {
  417. return rows
  418. }
  419. groupCnt, userCnt, err := AssignmentManager.fetchUserAndGroups(projIds)
  420. if err != nil {
  421. return rows
  422. }
  423. userMaps := make(map[string]SUser)
  424. err = db.FetchModelObjectsByIds(UserManager, "id", adminUserIds, &userMaps)
  425. if err != nil {
  426. log.Errorf("FetchModelObjectsByIds fail %s", err)
  427. }
  428. for i := range rows {
  429. groups, _ := groupCnt[projIds[i]]
  430. users, _ := userCnt[projIds[i]]
  431. rows[i].GroupCount = len(groups)
  432. rows[i].UserCount = len(users)
  433. rows[i].ExtResource, _ = extResource[projIds[i]]
  434. rows[i].ExtResourcesLastUpdate, _ = extLastUpdate[projIds[i]]
  435. if len(rows[i].ExtResource) == 0 {
  436. if rows[i].ExtResourcesLastUpdate.IsZero() {
  437. rows[i].ExtResourcesLastUpdate = time.Now()
  438. }
  439. nextUpdate := rows[i].ExtResourcesLastUpdate.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second)
  440. rows[i].ExtResourcesNextUpdate = nextUpdate
  441. }
  442. proj := objs[i].(*SProject)
  443. if len(proj.AdminId) > 0 {
  444. if user, ok := userMaps[proj.AdminId]; ok {
  445. rows[i].Admin = user.Name
  446. rows[i].AdminDomain = user.GetDomain().Name
  447. rows[i].AdminDomainId = user.DomainId
  448. }
  449. }
  450. projOrg, err := proj.matchOrganizationNodes()
  451. if err != nil {
  452. log.Errorf("matchOrganizationNodes fail %s", err)
  453. } else {
  454. rows[i].Organization = projOrg
  455. }
  456. }
  457. return rows
  458. }
  459. func NormalizeProjectName(name string) string {
  460. name = pinyinutils.Text2Pinyin(name)
  461. newName := strings.Builder{}
  462. lastSlash := false
  463. for _, c := range name {
  464. if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') {
  465. newName.WriteRune(c)
  466. lastSlash = false
  467. } else if c >= 'A' && c <= 'Z' {
  468. newName.WriteRune(c - 'A' + 'a')
  469. lastSlash = false
  470. } else if !lastSlash {
  471. newName.WriteRune('-')
  472. lastSlash = true
  473. }
  474. }
  475. return newName.String()
  476. }
  477. func (manager *SProjectManager) FetchUserProjects(userId string) ([]SProjectExtended, error) {
  478. projects := manager.Query().SubQuery()
  479. domains := DomainManager.Query().SubQuery()
  480. q := projects.Query(
  481. projects.Field("id"),
  482. projects.Field("name"),
  483. projects.Field("domain_id"),
  484. domains.Field("name").Label("domain_name"),
  485. )
  486. q = q.Join(domains, sqlchemy.Equals(projects.Field("domain_id"), domains.Field("id")))
  487. subq := AssignmentManager.fetchUserProjectIdsQuery(userId)
  488. q = q.Filter(sqlchemy.In(projects.Field("id"), subq))
  489. ret := make([]SProjectExtended, 0)
  490. err := q.All(&ret)
  491. if err != nil && err != sql.ErrNoRows {
  492. return nil, errors.Wrap(err, "query.All")
  493. }
  494. for i := range ret {
  495. ret[i].SetModelManager(manager, &ret[i])
  496. }
  497. return ret, nil
  498. }
  499. func (manager *SProjectManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ProjectCreateInput) (api.ProjectCreateInput, error) {
  500. err := db.ValidateCreateDomainId(ownerId.GetProjectDomainId())
  501. if err != nil {
  502. return input, errors.Wrap(err, "ValidateCreateDomainId")
  503. }
  504. input.IdentityBaseResourceCreateInput, err = manager.SIdentityBaseResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.IdentityBaseResourceCreateInput)
  505. if err != nil {
  506. return input, errors.Wrap(err, "SIdentityBaseResourceManager.ValidateCreateData")
  507. }
  508. quota := &SIdentityQuota{Project: 1}
  509. quota.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()})
  510. err = quotas.CheckSetPendingQuota(ctx, userCred, quota)
  511. if err != nil {
  512. return input, errors.Wrap(err, "CheckSetPendingQuota")
  513. }
  514. return input, nil
  515. }
  516. func (project *SProject) PostCreate(
  517. ctx context.Context,
  518. userCred mcclient.TokenCredential,
  519. ownerId mcclient.IIdentityProvider,
  520. query jsonutils.JSONObject,
  521. data jsonutils.JSONObject,
  522. ) {
  523. project.SIdentityBaseResource.PostCreate(ctx, userCred, ownerId, query, data)
  524. quota := &SIdentityQuota{Project: 1}
  525. quota.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: ownerId.GetProjectDomainId()})
  526. err := quotas.CancelPendingUsage(ctx, userCred, quota, quota, true)
  527. if err != nil {
  528. log.Errorf("CancelPendingUsage fail %s", err)
  529. }
  530. notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
  531. Obj: project,
  532. Action: notifyclient.ActionCreate,
  533. })
  534. }
  535. func threeMemberSystemValidatePolicies(userCred mcclient.TokenCredential, projectId string, assignPolicies rbacutils.TPolicyGroup) error {
  536. assignScope := assignPolicies.HighestScope()
  537. var checkRoles []string
  538. if assignScope == rbacscope.ScopeSystem {
  539. checkRoles = options.Options.SystemThreeAdminRoleNames
  540. } else if assignScope == rbacscope.ScopeDomain {
  541. checkRoles = options.Options.DomainThreeAdminRoleNames
  542. } else {
  543. return nil
  544. }
  545. var contains []string
  546. for _, roleName := range checkRoles {
  547. role, err := RoleManager.FetchRoleByName(roleName, "", "")
  548. if err != nil {
  549. return httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), roleName)
  550. }
  551. _, adminPolicies, _ := RolePolicyManager.GetMatchPolicyGroup2(false, []string{role.Id}, projectId, "", time.Time{}, false)
  552. if adminPolicies[assignScope].Contains(assignPolicies[assignScope]) {
  553. contains = append(contains, roleName)
  554. }
  555. }
  556. if len(contains) != 1 {
  557. return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "assigning roles violates three-member policy: %s", contains)
  558. }
  559. return nil
  560. }
  561. func normalValidatePolicies(userCred mcclient.TokenCredential, assignPolicies rbacutils.TPolicyGroup) error {
  562. _, opsPolicies, err := RolePolicyManager.GetMatchPolicyGroup(userCred, time.Time{}, false)
  563. if err != nil {
  564. return errors.Wrap(err, "RolePolicyManager.GetMatchPolicyGroup")
  565. }
  566. opsScope := opsPolicies.HighestScope()
  567. assignScope := assignPolicies.HighestScope()
  568. if assignScope.HigherThan(opsScope) {
  569. return errors.Wrap(httperrors.ErrNotSufficientPrivilege, "assigning roles requires higher privilege scope")
  570. } else if assignScope == opsScope && !opsPolicies[opsScope].Contains(assignPolicies[assignScope]) {
  571. return errors.Wrap(httperrors.ErrNotSufficientPrivilege, "assigning roles violates operator's policy")
  572. }
  573. return nil
  574. }
  575. func validateAssignPolicies(userCred mcclient.TokenCredential, projectId string, assignPolicies rbacutils.TPolicyGroup) error {
  576. if options.Options.NoPolicyViolationCheck {
  577. return nil
  578. }
  579. if options.Options.ThreeAdminRoleSystem {
  580. return threeMemberSystemValidatePolicies(userCred, projectId, assignPolicies)
  581. } else {
  582. return normalValidatePolicies(userCred, assignPolicies)
  583. }
  584. }
  585. func validateJoinProject(userCred mcclient.TokenCredential, project *SProject, roleIds []string) error {
  586. return ValidateJoinProjectRoles(userCred, project.Id, roleIds)
  587. }
  588. func ValidateJoinProjectRoles(userCred mcclient.TokenCredential, projectId string, roleIds []string) error {
  589. _, assignPolicies, err := RolePolicyManager.GetMatchPolicyGroup2(false, roleIds, projectId, "", time.Time{}, false)
  590. if err != nil {
  591. return errors.Wrap(err, "RolePolicyManager.GetMatchPolicyGroup2")
  592. }
  593. return validateAssignPolicies(userCred, projectId, assignPolicies)
  594. }
  595. // 将用户或组加入项目
  596. func (project *SProject) PerformJoin(
  597. ctx context.Context,
  598. userCred mcclient.TokenCredential,
  599. query jsonutils.JSONObject,
  600. input api.SProjectAddUserGroupInput,
  601. ) (jsonutils.JSONObject, error) {
  602. err := input.Validate()
  603. if err != nil {
  604. return nil, httperrors.NewInputParameterError("%v", err)
  605. }
  606. roleIds := make([]string, 0)
  607. roles := make([]*SRole, 0)
  608. for i := range input.Roles {
  609. obj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.Roles[i])
  610. if err != nil {
  611. if err == sql.ErrNoRows {
  612. return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.Roles[i])
  613. } else {
  614. return nil, httperrors.NewGeneralError(err)
  615. }
  616. }
  617. role := obj.(*SRole)
  618. roles = append(roles, role)
  619. roleIds = append(roleIds, role.Id)
  620. }
  621. err = validateJoinProject(userCred, project, roleIds)
  622. if err != nil {
  623. return nil, errors.Wrap(err, "validateJoinProject")
  624. }
  625. users := make([]*SUser, 0)
  626. for i := range input.Users {
  627. obj, err := UserManager.FetchByIdOrName(ctx, userCred, input.Users[i])
  628. if err != nil {
  629. if err == sql.ErrNoRows {
  630. return nil, httperrors.NewResourceNotFoundError2(UserManager.Keyword(), input.Users[i])
  631. } else {
  632. return nil, httperrors.NewGeneralError(err)
  633. }
  634. }
  635. users = append(users, obj.(*SUser))
  636. }
  637. groups := make([]*SGroup, 0)
  638. for i := range input.Groups {
  639. obj, err := GroupManager.FetchByIdOrName(ctx, userCred, input.Groups[i])
  640. if err != nil {
  641. if err == sql.ErrNoRows {
  642. return nil, httperrors.NewResourceNotFoundError2(GroupManager.Keyword(), input.Groups[i])
  643. } else {
  644. return nil, httperrors.NewGeneralError(err)
  645. }
  646. }
  647. groups = append(groups, obj.(*SGroup))
  648. }
  649. for i := range users {
  650. for j := range roles {
  651. err = AssignmentManager.ProjectAddUser(ctx, userCred, project, users[i], roles[j])
  652. if err != nil {
  653. return nil, httperrors.NewGeneralError(err)
  654. }
  655. }
  656. }
  657. for i := range groups {
  658. for j := range roles {
  659. err = AssignmentManager.projectAddGroup(ctx, userCred, project, groups[i], roles[j])
  660. if err != nil {
  661. return nil, httperrors.NewGeneralError(err)
  662. }
  663. }
  664. }
  665. if input.EnableAllUsers {
  666. for i := range users {
  667. db.EnabledPerformEnable(users[i], ctx, userCred, true)
  668. }
  669. }
  670. return nil, nil
  671. }
  672. // 将用户或组移出项目
  673. func (project *SProject) PerformLeave(
  674. ctx context.Context,
  675. userCred mcclient.TokenCredential,
  676. query jsonutils.JSONObject,
  677. input api.SProjectRemoveUserGroupInput,
  678. ) (jsonutils.JSONObject, error) {
  679. err := input.Validate()
  680. if err != nil {
  681. return nil, httperrors.NewInputParameterError("%v", err)
  682. }
  683. for i := range input.UserRoles {
  684. userObj, err := UserManager.FetchByIdOrName(ctx, userCred, input.UserRoles[i].User)
  685. if err != nil {
  686. if err == sql.ErrNoRows {
  687. return nil, httperrors.NewResourceNotFoundError2(UserManager.Keyword(), input.UserRoles[i].User)
  688. } else {
  689. return nil, httperrors.NewGeneralError(err)
  690. }
  691. }
  692. roleObj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.UserRoles[i].Role)
  693. if err != nil {
  694. if err == sql.ErrNoRows {
  695. return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.UserRoles[i].Role)
  696. } else {
  697. return nil, httperrors.NewGeneralError(err)
  698. }
  699. }
  700. err = AssignmentManager.projectRemoveUser(ctx, userCred, project, userObj.(*SUser), roleObj.(*SRole))
  701. if err != nil {
  702. return nil, httperrors.NewGeneralError(err)
  703. }
  704. }
  705. for i := range input.GroupRoles {
  706. groupObj, err := GroupManager.FetchByIdOrName(ctx, userCred, input.GroupRoles[i].Group)
  707. if err != nil {
  708. if err == sql.ErrNoRows {
  709. return nil, httperrors.NewResourceNotFoundError2(GroupManager.Keyword(), input.GroupRoles[i].Group)
  710. } else {
  711. return nil, httperrors.NewGeneralError(err)
  712. }
  713. }
  714. roleObj, err := RoleManager.FetchByIdOrName(ctx, userCred, input.GroupRoles[i].Role)
  715. if err != nil {
  716. if err == sql.ErrNoRows {
  717. return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), input.GroupRoles[i].Role)
  718. } else {
  719. return nil, httperrors.NewGeneralError(err)
  720. }
  721. }
  722. err = AssignmentManager.projectRemoveGroup(ctx, userCred, project, groupObj.(*SGroup), roleObj.(*SRole))
  723. if err != nil {
  724. return nil, httperrors.NewGeneralError(err)
  725. }
  726. }
  727. return nil, nil
  728. }
  729. func (project *SProject) GetUsages() []db.IUsage {
  730. if project.Deleted {
  731. return nil
  732. }
  733. usage := SIdentityQuota{Project: 1}
  734. usage.SetKeys(quotas.SBaseDomainQuotaKeys{DomainId: project.DomainId})
  735. return []db.IUsage{
  736. &usage,
  737. }
  738. }
  739. func (manager *SProjectManager) NewProject(ctx context.Context, projectName string, desc string, domainId string) (*SProject, error) {
  740. project := &SProject{}
  741. project.SetModelManager(ProjectManager, project)
  742. ownerId := &db.SOwnerId{}
  743. if manager.NamespaceScope() == rbacscope.ScopeDomain {
  744. ownerId.DomainId = domainId
  745. }
  746. project.DomainId = domainId
  747. project.Description = desc
  748. project.IsDomain = tristate.False
  749. project.ParentId = domainId
  750. var err = func() error {
  751. lockman.LockRawObject(ctx, manager.Keyword(), "name")
  752. defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name")
  753. newName, err := db.GenerateName(ctx, ProjectManager, ownerId, projectName)
  754. if err != nil {
  755. // ignore the error
  756. log.Errorf("db.GenerateName error %s for default domain project %s", err, projectName)
  757. newName = projectName
  758. }
  759. project.Name = newName
  760. return ProjectManager.TableSpec().Insert(ctx, project)
  761. }()
  762. if err != nil {
  763. return nil, errors.Wrap(err, "Insert")
  764. }
  765. return project, nil
  766. }
  767. func (project *SProject) PerformSetAdmin(
  768. ctx context.Context,
  769. userCred mcclient.TokenCredential,
  770. query jsonutils.JSONObject,
  771. input api.SProjectSetAdminInput,
  772. ) (jsonutils.JSONObject, error) {
  773. // unset admin
  774. if len(input.UserId) == 0 {
  775. return nil, project.setAdminId(ctx, userCred, input.UserId)
  776. }
  777. var user *SUser
  778. var role *SRole
  779. {
  780. obj, err := UserManager.FetchByIdOrName(ctx, userCred, input.UserId)
  781. if err != nil {
  782. if err == sql.ErrNoRows {
  783. return nil, httperrors.NewResourceNotFoundError2(UserManager.Keyword(), input.UserId)
  784. } else {
  785. return nil, httperrors.NewGeneralError(err)
  786. }
  787. }
  788. user = obj.(*SUser)
  789. }
  790. {
  791. obj, err := RoleManager.FetchByIdOrName(ctx, userCred, options.Options.ProjectAdminRole)
  792. if err != nil {
  793. if err == sql.ErrNoRows {
  794. return nil, httperrors.NewResourceNotFoundError2(RoleManager.Keyword(), options.Options.ProjectAdminRole)
  795. } else {
  796. return nil, httperrors.NewGeneralError(err)
  797. }
  798. }
  799. role = obj.(*SRole)
  800. }
  801. inProject, err := AssignmentManager.isUserInProjectWithRole(user.Id, project.Id, role.Id)
  802. if err != nil {
  803. return nil, errors.Wrap(err, "isUserInProjectWithRole")
  804. }
  805. if !inProject {
  806. err = AssignmentManager.ProjectAddUser(ctx, userCred, project, user, role)
  807. if err != nil {
  808. return nil, httperrors.NewGeneralError(err)
  809. }
  810. }
  811. err = project.setAdminId(ctx, userCred, user.Id)
  812. if err != nil {
  813. return nil, errors.Wrap(err, "setAdminId")
  814. }
  815. return nil, nil
  816. }
  817. func (project *SProject) setAdminId(ctx context.Context, userCred mcclient.TokenCredential, userId string) error {
  818. if project.AdminId != userId {
  819. diff, err := db.Update(project, func() error {
  820. project.AdminId = userId
  821. return nil
  822. })
  823. if err != nil {
  824. return errors.Wrap(err, "update adminId")
  825. }
  826. db.OpsLog.LogEvent(project, db.ACT_UPDATE, diff, userCred)
  827. logclient.AddSimpleActionLog(project, logclient.ACT_UPDATE, diff, userCred, true)
  828. }
  829. return nil
  830. }
  831. func (project *SProject) matchOrganizationNodes() (*api.SProjectOrganization, error) {
  832. orgs, err := OrganizationManager.FetchOrgnaizations(func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
  833. q = q.Equals("type", api.OrgTypeProject)
  834. q = q.IsTrue("enabled")
  835. return q
  836. })
  837. if err != nil {
  838. return nil, errors.Wrap(err, "FetchOrgnaizations")
  839. }
  840. if len(orgs) == 0 {
  841. return nil, nil
  842. } else if len(orgs) > 1 {
  843. return nil, errors.Wrap(httperrors.ErrDuplicateResource, "multiple enabled organizations")
  844. }
  845. org := &orgs[0]
  846. tags, err := project.GetAllOrganizationMetadata()
  847. if err != nil {
  848. return nil, errors.Wrap(err, "GetAllOrganizationMetadata")
  849. }
  850. if len(tags) == 0 {
  851. return nil, nil
  852. }
  853. log.Debugf("matchOrganizationNodes %s", jsonutils.Marshal(tags))
  854. projOrg, err := org.getProjectOrganization(tags)
  855. if err != nil {
  856. return nil, errors.Wrap(err, "getProjectOrganization")
  857. }
  858. return projOrg, nil
  859. }
  860. func (manager *SProjectManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  861. if userCred != nil && scope != rbacscope.ScopeSystem && scope != rbacscope.ScopeDomain {
  862. q = q.Equals("id", owner.GetProjectId())
  863. }
  864. return manager.SIdentityBaseResourceManager.FilterByOwner(ctx, q, man, userCred, owner, scope)
  865. }
  866. func (manager *SProjectManager) GetSystemProject() (*SProject, error) {
  867. q := manager.Query().Equals("name", api.SystemAdminProject)
  868. ret := &SProject{}
  869. ret.SetModelManager(manager, ret)
  870. err := q.First(ret)
  871. if err != nil {
  872. return nil, err
  873. }
  874. return ret, nil
  875. }
  876. func (self *SProject) StartProjectCleanTask(ctx context.Context, userCred mcclient.TokenCredential) error {
  877. task, err := taskman.TaskManager.NewTask(ctx, "ProjectCleanTask", self, userCred, nil, "", "", nil)
  878. if err != nil {
  879. return err
  880. }
  881. return task.ScheduleRun(nil)
  882. }
  883. func (self *SProject) GetEmptyProjects() ([]SProject, error) {
  884. q := ProjectManager.Query().IsFalse("pending_deleted").NotEquals("name", api.SystemAdminProject)
  885. scopes := []SScopeResource{}
  886. ScopeResourceManager.Query().GT("count", 0).All(&scopes)
  887. ids := []string{}
  888. for _, scope := range scopes {
  889. ids = append(ids, scope.ProjectId)
  890. }
  891. projects := []SProject{}
  892. if len(ids) == 0 {
  893. return projects, nil
  894. }
  895. q = q.Filter(sqlchemy.NotIn(q.Field("id"), ids))
  896. err := db.FetchModelObjects(ProjectManager, q, &projects)
  897. if err != nil {
  898. return nil, err
  899. }
  900. return projects, nil
  901. }
  902. func (manager *SProjectManager) PerformClean(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ProjectCleanInput) (jsonutils.JSONObject, error) {
  903. if !userCred.HasSystemAdminPrivilege() {
  904. return nil, httperrors.NewForbiddenError("not allow clean projects")
  905. }
  906. system, err := manager.GetSystemProject()
  907. if err != nil {
  908. return nil, err
  909. }
  910. return nil, system.StartProjectCleanTask(ctx, userCred)
  911. }