scoperesource.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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 db
  15. import (
  16. "context"
  17. "yunion.io/x/jsonutils"
  18. "yunion.io/x/pkg/errors"
  19. "yunion.io/x/pkg/util/rbacscope"
  20. "yunion.io/x/pkg/util/reflectutils"
  21. "yunion.io/x/pkg/utils"
  22. "yunion.io/x/sqlchemy"
  23. "yunion.io/x/onecloud/pkg/apis"
  24. "yunion.io/x/onecloud/pkg/httperrors"
  25. "yunion.io/x/onecloud/pkg/mcclient"
  26. "yunion.io/x/onecloud/pkg/util/stringutils2"
  27. )
  28. type SScopedResourceBaseManager struct {
  29. SProjectizedResourceBaseManager
  30. }
  31. // +onecloud:model-api-gen
  32. type SScopedResourceBase struct {
  33. SProjectizedResourceBase `domain_id->default:"" nullable:"true" tenant_id->default:"" nullable:"true"`
  34. }
  35. type sUniqValues struct {
  36. Scope string
  37. Project string
  38. Domain string
  39. }
  40. func (m *SScopedResourceBaseManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
  41. parentScope := rbacscope.ScopeSystem
  42. scope, _ := data.GetString("scope")
  43. if scope != "" {
  44. parentScope = rbacscope.TRbacScope(scope)
  45. }
  46. uniqValues := sUniqValues{}
  47. switch parentScope {
  48. case rbacscope.ScopeSystem:
  49. case rbacscope.ScopeDomain:
  50. domain, _ := data.GetString("project_domain")
  51. uniqValues.Domain = domain
  52. case rbacscope.ScopeProject:
  53. project, _ := data.GetString("project")
  54. uniqValues.Project = project
  55. }
  56. uniqValues.Scope = scope
  57. return jsonutils.Marshal(uniqValues)
  58. }
  59. func (m *SScopedResourceBaseManager) FilterByScope(q *sqlchemy.SQuery, scope rbacscope.TRbacScope, scopeResId string) *sqlchemy.SQuery {
  60. isNotNullOrEmpty := func(field string) sqlchemy.ICondition {
  61. return sqlchemy.AND(sqlchemy.IsNotNull(q.Field(field)), sqlchemy.IsNotEmpty(q.Field(field)))
  62. }
  63. switch scope {
  64. case rbacscope.ScopeSystem:
  65. q = q.IsNullOrEmpty("domain_id").IsNullOrEmpty("tenant_id")
  66. case rbacscope.ScopeDomain:
  67. q = q.IsNullOrEmpty("tenant_id").Filter(isNotNullOrEmpty("domain_id"))
  68. if scopeResId != "" {
  69. q = q.Equals("domain_id", scopeResId)
  70. }
  71. case rbacscope.ScopeProject:
  72. q = q.Filter(isNotNullOrEmpty("domain_id")).Filter(isNotNullOrEmpty("tenant_id"))
  73. if scopeResId != "" {
  74. q = q.Equals("tenant_id", scopeResId)
  75. }
  76. }
  77. return q
  78. }
  79. func (m *SScopedResourceBaseManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
  80. uniqValues := &sUniqValues{}
  81. values.Unmarshal(uniqValues)
  82. if len(uniqValues.Domain) > 0 {
  83. return m.FilterByScope(q, rbacscope.TRbacScope(uniqValues.Scope), uniqValues.Domain)
  84. } else if len(uniqValues.Project) > 0 {
  85. return m.FilterByScope(q, rbacscope.TRbacScope(uniqValues.Scope), uniqValues.Project)
  86. } else {
  87. return m.FilterByScope(q, rbacscope.TRbacScope(uniqValues.Scope), "")
  88. }
  89. }
  90. func (m *SScopedResourceBase) IsOwner(userCred mcclient.TokenCredential) bool {
  91. scope := m.GetResourceScope()
  92. switch scope {
  93. case rbacscope.ScopeDomain:
  94. return userCred.GetProjectDomainId() == m.GetDomainId()
  95. case rbacscope.ScopeProject:
  96. return userCred.GetProjectId() == m.GetProjectId()
  97. }
  98. // system scope
  99. return userCred.HasSystemAdminPrivilege()
  100. }
  101. func (m *SScopedResourceBaseManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  102. if owner == nil {
  103. return q
  104. }
  105. switch scope {
  106. case rbacscope.ScopeDomain:
  107. q = q.Equals("domain_id", owner.GetProjectDomainId())
  108. case rbacscope.ScopeProject:
  109. q = q.Equals("tenant_id", owner.GetProjectId())
  110. }
  111. return q
  112. }
  113. func (m *SScopedResourceBaseManager) ValidateCreateData(man IScopedResourceManager, ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input apis.ScopedResourceCreateInput) (apis.ScopedResourceCreateInput, error) {
  114. if input.Scope == "" {
  115. input.Scope = string(rbacscope.ScopeSystem)
  116. }
  117. if !utils.IsInStringArray(input.Scope, []string{
  118. string(rbacscope.ScopeSystem),
  119. string(rbacscope.ScopeDomain),
  120. string(rbacscope.ScopeProject)}) {
  121. return input, httperrors.NewInputParameterError("invalid scope %s", input.Scope)
  122. }
  123. var allowCreate bool
  124. switch rbacscope.TRbacScope(input.Scope) {
  125. case rbacscope.ScopeSystem:
  126. allowCreate = IsAdminAllowCreate(userCred, man).Result.IsAllow()
  127. case rbacscope.ScopeDomain:
  128. allowCreate = IsDomainAllowCreate(userCred, man).Result.IsAllow()
  129. input.ProjectDomainId = ownerId.GetDomainId()
  130. case rbacscope.ScopeProject:
  131. allowCreate = IsProjectAllowCreate(userCred, man).Result.IsAllow()
  132. input.ProjectDomainId = ownerId.GetDomainId()
  133. input.ProjectId = ownerId.GetProjectId()
  134. }
  135. if !allowCreate {
  136. return input, httperrors.NewForbiddenError("not allow create %s in scope %s", man.ResourceScope(), input.Scope)
  137. }
  138. return input, nil
  139. }
  140. func getScopedResourceScope(domainId, projectId string) rbacscope.TRbacScope {
  141. if domainId == "" && projectId == "" {
  142. return rbacscope.ScopeSystem
  143. }
  144. if domainId != "" && projectId == "" {
  145. return rbacscope.ScopeDomain
  146. }
  147. if domainId != "" && projectId != "" {
  148. return rbacscope.ScopeProject
  149. }
  150. return rbacscope.ScopeNone
  151. }
  152. func (s *SScopedResourceBase) GetResourceScope() rbacscope.TRbacScope {
  153. return getScopedResourceScope(s.DomainId, s.ProjectId)
  154. }
  155. func (s *SScopedResourceBase) GetDomainId() string {
  156. return s.DomainId
  157. }
  158. func (s *SScopedResourceBase) GetProjectId() string {
  159. return s.ProjectId
  160. }
  161. func (s *SScopedResourceBase) SetResourceScope(domainId, projectId string) error {
  162. s.DomainId = domainId
  163. s.ProjectId = projectId
  164. return nil
  165. }
  166. func (s *SScopedResourceBase) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  167. scope, _ := data.GetString("scope")
  168. switch rbacscope.TRbacScope(scope) {
  169. case rbacscope.ScopeSystem:
  170. s.DomainId = ""
  171. s.ProjectId = ""
  172. case rbacscope.ScopeDomain:
  173. s.DomainId = ownerId.GetDomainId()
  174. s.ProjectId = ""
  175. case rbacscope.ScopeProject:
  176. s.DomainId = ownerId.GetDomainId()
  177. s.ProjectId = ownerId.GetProjectId()
  178. }
  179. return nil
  180. }
  181. func (s *SScopedResourceBase) GetMoreColumns(extra *jsonutils.JSONDict) *jsonutils.JSONDict {
  182. if s.ProjectId != "" {
  183. extra.Add(jsonutils.NewString(s.ProjectId), "project_id")
  184. }
  185. return extra
  186. }
  187. type IScopedResourceModel interface {
  188. IModel
  189. GetDomainId() string
  190. GetProjectId() string
  191. SetResourceScope(domainId, projectId string) error
  192. }
  193. func PerformSetScope(
  194. ctx context.Context,
  195. obj IScopedResourceModel,
  196. userCred mcclient.TokenCredential,
  197. data jsonutils.JSONObject,
  198. ) (jsonutils.JSONObject, error) {
  199. domainId := jsonutils.GetAnyString(data, DomainFetchKeys) // []string{"domain_id", "domain", "project_domain_id", "project_domain"})
  200. projectId := jsonutils.GetAnyString(data, ProjectFetchKeys)
  201. if projectId != "" {
  202. project, err := DefaultProjectFetcher(ctx, projectId, domainId)
  203. if err != nil {
  204. return nil, err
  205. }
  206. projectId = project.GetProjectId()
  207. domainId = project.GetProjectDomainId()
  208. }
  209. if domainId != "" {
  210. domain, err := DefaultDomainFetcher(ctx, domainId)
  211. if err != nil {
  212. return nil, err
  213. }
  214. domainId = domain.GetProjectDomainId()
  215. }
  216. scopeToSet := getScopedResourceScope(domainId, projectId)
  217. var err error
  218. switch scopeToSet {
  219. case rbacscope.ScopeSystem:
  220. err = setScopedResourceToSystem(ctx, obj, userCred)
  221. case rbacscope.ScopeDomain:
  222. err = setScopedResourceToDomain(ctx, obj, userCred, domainId)
  223. case rbacscope.ScopeProject:
  224. err = setScopedResourceToProject(ctx, obj, userCred, projectId)
  225. }
  226. return nil, err
  227. }
  228. func setScopedResourceIds(model IScopedResourceModel, userCred mcclient.TokenCredential, domainId, projectId string) error {
  229. diff, err := Update(model, func() error {
  230. model.SetResourceScope(domainId, projectId)
  231. return nil
  232. })
  233. if err == nil {
  234. OpsLog.LogEvent(model, ACT_UPDATE, diff, userCred)
  235. }
  236. return err
  237. }
  238. func setScopedResourceToSystem(ctx context.Context, model IScopedResourceModel, userCred mcclient.TokenCredential) error {
  239. if !IsAdminAllowPerform(ctx, userCred, model, "set-scope") {
  240. return httperrors.NewForbiddenError("Not allow set scope to system")
  241. }
  242. if model.GetProjectId() == "" && model.GetDomainId() == "" {
  243. return nil
  244. }
  245. return setScopedResourceIds(model, userCred, "", "")
  246. }
  247. func setScopedResourceToDomain(ctx context.Context, model IScopedResourceModel, userCred mcclient.TokenCredential, domainId string) error {
  248. if !IsDomainAllowPerform(ctx, userCred, model, "set-scope") {
  249. return httperrors.NewForbiddenError("Not allow set scope to domain %s", domainId)
  250. }
  251. if model.GetDomainId() == domainId && model.GetProjectId() == "" {
  252. return nil
  253. }
  254. domain, err := TenantCacheManager.FetchDomainById(context.TODO(), domainId)
  255. if err != nil {
  256. return err
  257. }
  258. return setScopedResourceIds(model, userCred, domain.GetId(), "")
  259. }
  260. func setScopedResourceToProject(ctx context.Context, model IScopedResourceModel, userCred mcclient.TokenCredential, projectId string) error {
  261. if !IsProjectAllowPerform(ctx, userCred, model, "set-scope") {
  262. return httperrors.NewForbiddenError("Not allow set scope to project %s", projectId)
  263. }
  264. if model.GetProjectId() == projectId {
  265. return nil
  266. }
  267. project, err := TenantCacheManager.FetchTenantById(context.TODO(), projectId)
  268. if err != nil {
  269. return err
  270. }
  271. return setScopedResourceIds(model, userCred, project.GetProjectDomainId(), projectId)
  272. }
  273. func (m *SScopedResourceBaseManager) ListItemFilter(
  274. ctx context.Context,
  275. q *sqlchemy.SQuery,
  276. userCred mcclient.TokenCredential,
  277. query apis.ScopedResourceBaseListInput,
  278. ) (*sqlchemy.SQuery, error) {
  279. q, err := m.SProjectizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ProjectizedResourceListInput)
  280. if err != nil {
  281. return nil, errors.Wrap(err, "SProjectizedResourceBaseManager.ListItemFilter")
  282. }
  283. if query.BelongScope != "" {
  284. q = m.FilterByScope(q, rbacscope.TRbacScope(query.BelongScope), "")
  285. }
  286. return q, nil
  287. }
  288. func (m *SScopedResourceBaseManager) OrderByExtraFields(
  289. ctx context.Context,
  290. q *sqlchemy.SQuery,
  291. userCred mcclient.TokenCredential,
  292. query apis.ScopedResourceBaseListInput,
  293. ) (*sqlchemy.SQuery, error) {
  294. q, err := m.SProjectizedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ProjectizedResourceListInput)
  295. if err != nil {
  296. return nil, errors.Wrap(err, "SProjectizedResourceBaseManager.OrderByExtraFields")
  297. }
  298. return q, nil
  299. }
  300. func (manager *SScopedResourceBaseManager) FetchCustomizeColumns(
  301. ctx context.Context,
  302. userCred mcclient.TokenCredential,
  303. query jsonutils.JSONObject,
  304. objs []interface{},
  305. fields stringutils2.SSortedStrings,
  306. isList bool,
  307. ) []apis.ScopedResourceBaseInfo {
  308. rows := make([]apis.ScopedResourceBaseInfo, len(objs))
  309. projRows := manager.SProjectizedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  310. for i := range rows {
  311. rows[i] = apis.ScopedResourceBaseInfo{
  312. ProjectizedResourceInfo: projRows[i],
  313. }
  314. var base *SScopedResourceBase
  315. reflectutils.FindAnonymouStructPointer(objs[i], &base)
  316. if base != nil {
  317. if base.ProjectId != "" {
  318. project, _ := DefaultProjectFetcher(ctx, base.ProjectId, base.DomainId)
  319. if project != nil {
  320. rows[i].Project = project.Name
  321. rows[i].ProjectDomain = project.Domain
  322. }
  323. } else if base.DomainId != "" {
  324. domain, _ := DefaultDomainFetcher(ctx, base.DomainId)
  325. if domain != nil {
  326. rows[i].ProjectDomain = domain.Name
  327. }
  328. }
  329. rows[i].Scope = string(base.GetResourceScope())
  330. }
  331. }
  332. return rows
  333. }