scopedpolicybindings.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. "strings"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/pkg/errors"
  21. "yunion.io/x/pkg/util/rbacscope"
  22. "yunion.io/x/sqlchemy"
  23. api "yunion.io/x/onecloud/pkg/apis/yunionconf"
  24. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  25. "yunion.io/x/onecloud/pkg/cloudcommon/policy"
  26. "yunion.io/x/onecloud/pkg/httperrors"
  27. "yunion.io/x/onecloud/pkg/mcclient"
  28. "yunion.io/x/onecloud/pkg/util/stringutils2"
  29. )
  30. // +onecloud:swagger-gen-model-singular=scopedpolicybinding
  31. // +onecloud:swagger-gen-model-plural=scopedpolicybindings
  32. type SScopedPolicyBindingManager struct {
  33. db.SResourceBaseManager
  34. }
  35. type SScopedPolicyBinding struct {
  36. db.SResourceBase
  37. Category string `width:"64" charset:"utf8" nullable:"false" primary:"true" list:"user"`
  38. DomainId string `width:"128" charset:"ascii" nullable:"false" primary:"true" list:"user"`
  39. ProjectId string `width:"128" charset:"ascii" nullable:"false" primary:"true" list:"user"`
  40. PolicyId string `width:"128" charset:"ascii" nullable:"false" list:"user"`
  41. Priority int `nullable:"false" list:"user"`
  42. }
  43. var ScopedPolicyBindingManager *SScopedPolicyBindingManager
  44. func init() {
  45. ScopedPolicyBindingManager = &SScopedPolicyBindingManager{
  46. SResourceBaseManager: db.NewResourceBaseManager(
  47. SScopedPolicyBinding{},
  48. "scopedpolicybinding_tbl",
  49. "scopedpolicybinding",
  50. "scopedpolicybindings",
  51. ),
  52. }
  53. ScopedPolicyBindingManager.SetVirtualObject(ScopedPolicyBindingManager)
  54. }
  55. func (binding *SScopedPolicyBinding) GetId() string {
  56. return strings.Join([]string{binding.Category, binding.DomainId, binding.ProjectId}, ":")
  57. }
  58. func (binding *SScopedPolicyBinding) GetName() string {
  59. return binding.GetId()
  60. }
  61. func (binding *SScopedPolicyBinding) calculatePriority() (int, error) {
  62. if binding.DomainId == "" && binding.ProjectId == "" {
  63. // system level
  64. return 0, nil
  65. } else if len(binding.DomainId) > 0 && binding.ProjectId == "" {
  66. // domain level
  67. if binding.DomainId == api.ANY_DOMAIN_ID {
  68. return 10, nil
  69. } else {
  70. return 11, nil
  71. }
  72. } else if len(binding.DomainId) > 0 && len(binding.ProjectId) > 0 {
  73. // project level
  74. if binding.ProjectId == api.ANY_PROJECT_ID {
  75. if binding.DomainId == api.ANY_DOMAIN_ID {
  76. return 100, nil
  77. } else {
  78. return 101, nil
  79. }
  80. } else {
  81. if binding.DomainId == api.ANY_DOMAIN_ID {
  82. return -1, errors.Wrap(httperrors.ErrInvalidStatus, "domain_id cannot be any domain when project_id is specific")
  83. } else {
  84. return 102, nil
  85. }
  86. }
  87. } else {
  88. return -1, errors.Wrap(httperrors.ErrInvalidStatus, "invalid domain_id and project_id")
  89. }
  90. }
  91. func (manager *SScopedPolicyBindingManager) bind(ctx context.Context, category, policyId, domainId, projectId string) error {
  92. binding := SScopedPolicyBinding{
  93. Category: category,
  94. DomainId: domainId,
  95. ProjectId: projectId,
  96. PolicyId: policyId,
  97. }
  98. var err error
  99. binding.Priority, err = binding.calculatePriority()
  100. if err != nil {
  101. return errors.Wrap(err, "calculatePriority")
  102. }
  103. binding.SetModelManager(manager, &binding)
  104. err = manager.TableSpec().InsertOrUpdate(ctx, &binding)
  105. if err != nil {
  106. return errors.Wrap(err, "InsertOrUpdate")
  107. }
  108. return nil
  109. }
  110. func (manager *SScopedPolicyBindingManager) unbind(ctx context.Context, category, domainId, projectId string) error {
  111. binding := SScopedPolicyBinding{
  112. Category: category,
  113. DomainId: domainId,
  114. ProjectId: projectId,
  115. }
  116. binding.SetModelManager(manager, &binding)
  117. _, err := db.Update(&binding, func() error {
  118. return binding.MarkDelete()
  119. })
  120. if err != nil {
  121. if errors.Cause(err) == sql.ErrNoRows {
  122. return nil
  123. }
  124. return errors.Wrap(err, "MarkDelete")
  125. }
  126. return nil
  127. }
  128. func (manager *SScopedPolicyBindingManager) ResourceScope() rbacscope.TRbacScope {
  129. return rbacscope.ScopeProject
  130. }
  131. func (manager *SScopedPolicyBindingManager) getReferenceCount(policyId string) (int, error) {
  132. q := manager.Query().Equals("policy_id", policyId)
  133. return q.CountWithError()
  134. }
  135. // 范围策略列表
  136. func (manager *SScopedPolicyBindingManager) ListItemFilter(
  137. ctx context.Context,
  138. q *sqlchemy.SQuery,
  139. userCred mcclient.TokenCredential,
  140. query api.ScopedPolicyBindingListInput,
  141. ) (*sqlchemy.SQuery, error) {
  142. var err error
  143. q, err = manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ResourceBaseListInput)
  144. if err != nil {
  145. return nil, errors.Wrap(err, "SResourceBaseManager.ListItemFilter")
  146. }
  147. var policySubq *sqlchemy.SSubQuery
  148. if len(query.Name) > 0 {
  149. subq := ScopedPolicyManager.Query("id")
  150. subq = subq.Filter(sqlchemy.ContainsAny(subq.Field("name"), query.Name))
  151. policySubq = subq.SubQuery()
  152. }
  153. if len(query.PolicyId) > 0 {
  154. policyObj, err := ScopedPolicyManager.FetchByIdOrName(ctx, userCred, query.PolicyId)
  155. if err != nil {
  156. if errors.Cause(err) == sql.ErrNoRows {
  157. return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", ScopedPolicyManager.Keyword(), query.PolicyId)
  158. } else {
  159. return nil, errors.Wrap(err, "ScopedPolicyManager.FetchByIdOrName")
  160. }
  161. }
  162. query.PolicyId = policyObj.GetId()
  163. }
  164. if len(query.ProjectId) > 0 {
  165. projObj, err := db.TenantCacheManager.FetchTenantByIdOrNameInDomain(ctx, query.ProjectId, query.DomainId)
  166. if err != nil {
  167. return nil, errors.Wrap(err, "TenantCacheManager.FetchTenantByIdOrName")
  168. }
  169. query.DomainId = projObj.DomainId
  170. query.ProjectId = projObj.Id
  171. } else if len(query.DomainId) > 0 {
  172. domainObj, err := db.TenantCacheManager.FetchDomainByIdOrName(ctx, query.DomainId)
  173. if err != nil {
  174. return nil, errors.Wrap(err, "TenantCacheManager.FetchDomainByIdOrName")
  175. }
  176. query.DomainId = domainObj.Id
  177. } else {
  178. if len(query.Scope) == 0 {
  179. query.Scope = rbacscope.ScopeProject
  180. }
  181. switch query.Scope {
  182. case rbacscope.ScopeProject:
  183. query.ProjectId = userCred.GetProjectId()
  184. query.DomainId = userCred.GetProjectDomainId()
  185. case rbacscope.ScopeDomain:
  186. query.DomainId = userCred.GetProjectDomainId()
  187. }
  188. }
  189. var requireScope rbacscope.TRbacScope
  190. if len(query.ProjectId) > 0 {
  191. if query.ProjectId == userCred.GetProjectId() {
  192. // require project privileges
  193. requireScope = rbacscope.ScopeProject
  194. } else if query.DomainId == userCred.GetProjectDomainId() {
  195. // require domain privileges
  196. requireScope = rbacscope.ScopeDomain
  197. } else {
  198. // require system privileges
  199. requireScope = rbacscope.ScopeSystem
  200. }
  201. } else if len(query.DomainId) > 0 {
  202. if query.DomainId == userCred.GetProjectDomainId() {
  203. // require domain privileges
  204. requireScope = rbacscope.ScopeDomain
  205. } else {
  206. // require system privileges
  207. requireScope = rbacscope.ScopeSystem
  208. }
  209. } else {
  210. requireScope = rbacscope.ScopeSystem
  211. }
  212. allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, manager.KeywordPlural(), policy.PolicyActionList)
  213. if requireScope.HigherThan(allowScope) {
  214. return nil, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require: %s allow: %s", requireScope, allowScope)
  215. }
  216. effective := (query.Effective != nil && *query.Effective)
  217. q = filterByOwnerId(q, query.DomainId, query.ProjectId, effective, query.Category, query.PolicyId, policySubq)
  218. if query.Effective != nil && *query.Effective && (len(query.ProjectId) > 0 || len(query.DomainId) > 0) {
  219. bindingQ := manager.Query().SubQuery()
  220. maxq := bindingQ.Query(bindingQ.Field("category"), sqlchemy.MAX("max_priority", bindingQ.Field("priority")))
  221. maxq = filterByOwnerId(maxq, query.DomainId, query.ProjectId, *query.Effective, query.Category, query.PolicyId, policySubq)
  222. maxq = maxq.GroupBy(bindingQ.Field("category"))
  223. subq := maxq.SubQuery()
  224. q = q.Join(subq, sqlchemy.Equals(q.Field("category"), subq.Field("category")))
  225. q = q.Filter(sqlchemy.Equals(q.Field("priority"), subq.Field("max_priority")))
  226. }
  227. return q, nil
  228. }
  229. func filterByOwnerId(q *sqlchemy.SQuery, domainId, projectId string, effective bool, category []string, policyId string, policySubq *sqlchemy.SSubQuery) *sqlchemy.SQuery {
  230. if len(projectId) > 0 {
  231. q = q.Filter(sqlchemy.OR(
  232. sqlchemy.AND(sqlchemy.IsNullOrEmpty(q.Field("domain_id")), sqlchemy.IsNullOrEmpty(q.Field("project_id"))),
  233. sqlchemy.AND(sqlchemy.Equals(q.Field("domain_id"), api.ANY_DOMAIN_ID), sqlchemy.OR(
  234. sqlchemy.Equals(q.Field("project_id"), api.ANY_PROJECT_ID),
  235. sqlchemy.IsNullOrEmpty(q.Field("project_id")),
  236. )),
  237. sqlchemy.AND(sqlchemy.Equals(q.Field("domain_id"), domainId), sqlchemy.OR(
  238. sqlchemy.IsNullOrEmpty(q.Field("project_id")),
  239. sqlchemy.Equals(q.Field("project_id"), api.ANY_PROJECT_ID),
  240. sqlchemy.Equals(q.Field("project_id"), projectId),
  241. )),
  242. ))
  243. } else if len(domainId) > 0 {
  244. q = q.IsNullOrEmpty("project_id")
  245. q = q.Filter(sqlchemy.OR(
  246. sqlchemy.IsNullOrEmpty(q.Field("domain_id")),
  247. sqlchemy.Equals(q.Field("domain_id"), api.ANY_DOMAIN_ID),
  248. sqlchemy.Equals(q.Field("domain_id"), domainId),
  249. ))
  250. } else if effective {
  251. q = q.IsNullOrEmpty("project_id")
  252. q = q.IsNullOrEmpty("domain_id")
  253. }
  254. if len(category) > 0 {
  255. q = q.In("category", category)
  256. }
  257. if len(policyId) > 0 {
  258. q = q.Equals("policy_id", policyId)
  259. }
  260. if policySubq != nil {
  261. q = q.In("policy_id", policySubq)
  262. }
  263. return q
  264. }
  265. func (manager *SScopedPolicyBindingManager) OrderByExtraFields(
  266. ctx context.Context,
  267. q *sqlchemy.SQuery,
  268. userCred mcclient.TokenCredential,
  269. query api.ScopedPolicyBindingListInput,
  270. ) (*sqlchemy.SQuery, error) {
  271. var err error
  272. q, err = manager.SResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ResourceBaseListInput)
  273. if err != nil {
  274. return nil, errors.Wrap(err, "SResourceBaseManager.OrderByExtraFields")
  275. }
  276. if db.NeedOrderQuery([]string{query.OrderByScopedpolicy}) {
  277. subq := ScopedPolicyManager.Query("id", "name").SubQuery()
  278. q = q.Join(subq, sqlchemy.Equals(subq.Field("id"), q.Field("policy_id")))
  279. q = db.OrderByFields(q,
  280. []string{query.OrderByScopedpolicy},
  281. []sqlchemy.IQueryField{subq.Field("name")})
  282. }
  283. return q, nil
  284. }
  285. func (manager *SScopedPolicyBindingManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
  286. var err error
  287. q, err = manager.SResourceBaseManager.QueryDistinctExtraField(q, field)
  288. if err == nil {
  289. return q, nil
  290. }
  291. return q, httperrors.ErrNotFound
  292. }
  293. func (manager *SScopedPolicyBindingManager) FetchCustomizeColumns(
  294. ctx context.Context,
  295. userCred mcclient.TokenCredential,
  296. query jsonutils.JSONObject,
  297. objs []interface{},
  298. fields stringutils2.SSortedStrings,
  299. isList bool,
  300. ) []api.ScopedPolicyBindingDetails {
  301. rows := make([]api.ScopedPolicyBindingDetails, len(objs))
  302. policyIds := make([]string, 0)
  303. domainIds := make([]string, 0)
  304. projectIds := make([]string, 0)
  305. stdRows := manager.SResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  306. for i := range rows {
  307. rows[i] = api.ScopedPolicyBindingDetails{
  308. ResourceBaseDetails: stdRows[i],
  309. }
  310. binding := objs[i].(*SScopedPolicyBinding)
  311. rows[i].Id = binding.GetId()
  312. policyIds = append(policyIds, binding.PolicyId)
  313. if len(binding.ProjectId) > 0 && binding.ProjectId != api.ANY_PROJECT_ID {
  314. projectIds = append(projectIds, binding.ProjectId)
  315. } else if len(binding.DomainId) > 0 && binding.DomainId != api.ANY_DOMAIN_ID {
  316. domainIds = append(domainIds, binding.DomainId)
  317. }
  318. }
  319. policies := make(map[string]SScopedPolicy)
  320. err := db.FetchModelObjectsByIds(ScopedPolicyManager, "id", policyIds, policies)
  321. if err != nil {
  322. return rows
  323. }
  324. domains := db.DefaultProjectsFetcher(ctx, domainIds, true)
  325. projects := db.DefaultProjectsFetcher(ctx, projectIds, false)
  326. for i := range rows {
  327. binding := objs[i].(*SScopedPolicyBinding)
  328. if policy, ok := policies[binding.PolicyId]; ok {
  329. rows[i].PolicyName = policy.Name
  330. rows[i].Policies = policy.Policies
  331. }
  332. if len(binding.ProjectId) > 0 && binding.ProjectId != api.ANY_PROJECT_ID {
  333. if project, ok := projects[binding.ProjectId]; ok {
  334. rows[i].Project = project.Name
  335. rows[i].ProjectDomain = project.Domain
  336. }
  337. } else if len(binding.DomainId) > 0 && binding.DomainId != api.ANY_DOMAIN_ID {
  338. if domain, ok := domains[binding.DomainId]; ok {
  339. rows[i].ProjectDomain = domain.Name
  340. }
  341. }
  342. }
  343. return rows
  344. }
  345. func (manager *SScopedPolicyBindingManager) ListItemExportKeys(ctx context.Context,
  346. q *sqlchemy.SQuery,
  347. userCred mcclient.TokenCredential,
  348. keys stringutils2.SSortedStrings,
  349. ) (*sqlchemy.SQuery, error) {
  350. var err error
  351. q, err = manager.SResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
  352. if err != nil {
  353. return nil, errors.Wrap(err, "SResourceBaseManager.ListItemExportKeys")
  354. }
  355. return q, nil
  356. }
  357. func (manager *SScopedPolicyBindingManager) FilterById(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery {
  358. parts := strings.Split(idStr, ":")
  359. if len(parts) == 3 {
  360. return q.Equals("category", parts[0]).Equals("domain_id", parts[1]).Equals("project_id", parts[2])
  361. } else {
  362. return q.Equals("category", idStr)
  363. }
  364. }
  365. func (manager *SScopedPolicyBindingManager) FilterByNotId(q *sqlchemy.SQuery, idStr string) *sqlchemy.SQuery {
  366. parts := strings.Split(idStr, ":")
  367. if len(parts) == 3 {
  368. return q.Filter(sqlchemy.OR(
  369. sqlchemy.NotEquals(q.Field("category"), parts[0]),
  370. sqlchemy.NotEquals(q.Field("domain_id"), parts[1]),
  371. sqlchemy.NotEquals(q.Field("project_id"), parts[2]),
  372. ))
  373. } else {
  374. return q
  375. }
  376. }
  377. func (manager *SScopedPolicyBindingManager) FilterByName(q *sqlchemy.SQuery, name string) *sqlchemy.SQuery {
  378. return manager.FilterById(q, name)
  379. }
  380. func (manager *SScopedPolicyBindingManager) GetResourceCount() ([]db.SScopeResourceCount, error) {
  381. cnt := make([]db.SScopeResourceCount, 0)
  382. projCnt, err := manager.getProjectResourceCount()
  383. if err != nil {
  384. return nil, errors.Wrap(err, "getProjectResourceCount")
  385. }
  386. domainCnt, err := manager.getDomainResourceCount()
  387. if err != nil {
  388. return nil, errors.Wrap(err, "getDomainResourceCount")
  389. }
  390. cnt = append(cnt, projCnt...)
  391. cnt = append(cnt, domainCnt...)
  392. return cnt, nil
  393. }
  394. func (manager *SScopedPolicyBindingManager) getProjectResourceCount() ([]db.SScopeResourceCount, error) {
  395. subq := manager.Query().SubQuery()
  396. q := subq.Query(subq.Field("project_id").Label("tenant_id")).IsNotEmpty("project_id").NotEquals("project_id", api.ANY_PROJECT_ID)
  397. return db.CalculateResourceCount(q, "tenant_id")
  398. }
  399. func (manager *SScopedPolicyBindingManager) getDomainResourceCount() ([]db.SScopeResourceCount, error) {
  400. subq := manager.Query().SubQuery()
  401. q := subq.Query(subq.Field("domain_id")).IsNotEmpty("domain_id").NotEquals("domain_id", api.ANY_DOMAIN_ID)
  402. return db.CalculateResourceCount(q, "domain_id")
  403. }