mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
fix: keystone-role-assignments-list-scope (#25652)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/keystone/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
@@ -584,6 +585,17 @@ func AddAdhocHandlers(version string, app *appsrv.Application) {
|
||||
}
|
||||
|
||||
func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
|
||||
if userCred == nil {
|
||||
httperrors.UnauthorizedError(ctx, w, "unauthorized")
|
||||
return
|
||||
}
|
||||
allowScope, policyResult := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, "role_assignments", policy.PolicyActionList)
|
||||
if policyResult.Result.IsDeny() {
|
||||
httperrors.ForbiddenError(ctx, w, "not allow to list role assignments")
|
||||
return
|
||||
}
|
||||
|
||||
_, query, _ := appsrv.FetchEnv(ctx, w, r)
|
||||
input := api.RoleAssignmentsInput{}
|
||||
err := query.Unmarshal(&input)
|
||||
@@ -592,6 +604,12 @@ func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
return
|
||||
}
|
||||
|
||||
restrictDomainId, err := checkRoleAssignmentListInput(allowScope, userCred, &input)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
includeNames := (input.IncludeNames != nil)
|
||||
effective := (input.Effective != nil)
|
||||
includeSub := (input.IncludeSubtree != nil)
|
||||
@@ -607,7 +625,7 @@ func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
offset = *input.Offset
|
||||
}
|
||||
|
||||
results, total, err := AssignmentManager.FetchAll(
|
||||
results, total, err := AssignmentManager.fetchAll(
|
||||
input.User.Id,
|
||||
input.Group.Id,
|
||||
input.Role.Id,
|
||||
@@ -620,6 +638,7 @@ func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
input.Domains,
|
||||
input.Projects,
|
||||
input.ProjectDomains,
|
||||
restrictDomainId,
|
||||
includeNames, effective, includeSub, includeSystem, includePolicies,
|
||||
limit, offset)
|
||||
|
||||
@@ -639,6 +658,7 @@ func roleAssignmentHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
func (manager *SAssignmentManager) queryAll(
|
||||
userId, groupId, roleId, domainId, projectId string, projectDomainId string,
|
||||
users, groups, roles, domains, projects, projectDomains []string,
|
||||
restrictDomainId string,
|
||||
) *sqlchemy.SQuery {
|
||||
assigments := manager.Query().SubQuery()
|
||||
q := assigments.Query(
|
||||
@@ -749,6 +769,13 @@ func (manager *SAssignmentManager) queryAll(
|
||||
))
|
||||
q = q.In("domain_id", subq.SubQuery()).In("type", []string{api.AssignmentUserDomain, api.AssignmentGroupDomain})
|
||||
}
|
||||
if len(restrictDomainId) > 0 {
|
||||
projSubq := ProjectManager.Query("id").Equals("domain_id", restrictDomainId).SubQuery()
|
||||
q = q.Filter(sqlchemy.OR(
|
||||
sqlchemy.In(q.Field("project_id"), projSubq),
|
||||
sqlchemy.Equals(q.Field("domain_id"), restrictDomainId),
|
||||
))
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
@@ -807,13 +834,27 @@ func (manager *SAssignmentManager) FetchAll(
|
||||
userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs []string,
|
||||
includeNames, effective, includeSub, includeSystem, includePolicies bool,
|
||||
limit, offset int) ([]api.SRoleAssignment, int64, error) {
|
||||
return manager.fetchAll(
|
||||
userId, groupId, roleId, domainId, projectId, projectDomainId,
|
||||
userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs,
|
||||
"",
|
||||
includeNames, effective, includeSub, includeSystem, includePolicies,
|
||||
limit, offset)
|
||||
}
|
||||
|
||||
func (manager *SAssignmentManager) fetchAll(
|
||||
userId, groupId, roleId, domainId, projectId string, projectDomainId string,
|
||||
userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs []string,
|
||||
restrictDomainId string,
|
||||
includeNames, effective, includeSub, includeSystem, includePolicies bool,
|
||||
limit, offset int) ([]api.SRoleAssignment, int64, error) {
|
||||
var q *sqlchemy.SQuery
|
||||
if effective {
|
||||
usrq := manager.queryAll(userId, "", roleId, domainId, projectId, projectDomainId, userStrs, nil, roleStrs, domainStrs, projectStrs, projectDomainStrs).In("type", []string{api.AssignmentUserProject, api.AssignmentUserDomain})
|
||||
usrq := manager.queryAll(userId, "", roleId, domainId, projectId, projectDomainId, userStrs, nil, roleStrs, domainStrs, projectStrs, projectDomainStrs, restrictDomainId).In("type", []string{api.AssignmentUserProject, api.AssignmentUserDomain})
|
||||
|
||||
memberships := UsergroupManager.Query("user_id", "group_id").SubQuery()
|
||||
|
||||
grpproj := manager.queryAll("", groupId, roleId, domainId, projectId, projectDomainId, nil, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs).In("type", []string{api.AssignmentGroupProject, api.AssignmentGroupDomain}).SubQuery()
|
||||
grpproj := manager.queryAll("", groupId, roleId, domainId, projectId, projectDomainId, nil, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs, restrictDomainId).In("type", []string{api.AssignmentGroupProject, api.AssignmentGroupDomain}).SubQuery()
|
||||
q2 := grpproj.Query(
|
||||
grpproj.Field("type"),
|
||||
memberships.Field("user_id"),
|
||||
@@ -837,7 +878,7 @@ func (manager *SAssignmentManager) FetchAll(
|
||||
|
||||
q = sqlchemy.Union(usrq, q2).Query().Distinct()
|
||||
} else {
|
||||
q = manager.queryAll(userId, groupId, roleId, domainId, projectId, projectDomainId, userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs).Distinct()
|
||||
q = manager.queryAll(userId, groupId, roleId, domainId, projectId, projectDomainId, userStrs, groupStrs, roleStrs, domainStrs, projectStrs, projectDomainStrs, restrictDomainId).Distinct()
|
||||
}
|
||||
|
||||
if !includeSystem {
|
||||
|
||||
67
pkg/keystone/models/assignments_scope.go
Normal file
67
pkg/keystone/models/assignments_scope.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"yunion.io/x/pkg/util/rbacscope"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
func checkRoleAssignmentListInput(allowScope rbacscope.TRbacScope, userCred mcclient.TokenCredential, input *api.RoleAssignmentsInput) (string, error) {
|
||||
switch allowScope {
|
||||
case rbacscope.ScopeSystem:
|
||||
return "", nil
|
||||
case rbacscope.ScopeDomain:
|
||||
domainId := userCred.GetProjectDomainId()
|
||||
if err := rejectRoleAssignmentMismatch(input.ProjectDomainId, domainId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := rejectRoleAssignmentMismatch(input.Scope.Domain.Id, domainId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return domainId, nil
|
||||
case rbacscope.ScopeProject:
|
||||
projectId := userCred.GetProjectId()
|
||||
if err := rejectRoleAssignmentMismatch(input.Scope.Project.Id, projectId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
input.Scope.Project.Id = projectId
|
||||
input.IncludePolicies = nil
|
||||
return "", nil
|
||||
case rbacscope.ScopeUser:
|
||||
userId := userCred.GetUserId()
|
||||
if err := rejectRoleAssignmentMismatch(input.User.Id, userId); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(input.Group.Id) > 0 || len(input.Groups) > 0 {
|
||||
return "", httperrors.NewForbiddenError("not allow to list role assignments")
|
||||
}
|
||||
input.User.Id = userId
|
||||
input.IncludePolicies = nil
|
||||
return "", nil
|
||||
default:
|
||||
return "", httperrors.NewForbiddenError("not allow to list role assignments")
|
||||
}
|
||||
}
|
||||
|
||||
func rejectRoleAssignmentMismatch(requested, allowed string) error {
|
||||
if len(requested) > 0 && requested != allowed {
|
||||
return httperrors.NewForbiddenError("not allow to list role assignments")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
120
pkg/keystone/models/assignments_scope_test.go
Normal file
120
pkg/keystone/models/assignments_scope_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"yunion.io/x/pkg/util/rbacscope"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
func testRoleAssignmentCred() *mcclient.SSimpleToken {
|
||||
return &mcclient.SSimpleToken{
|
||||
UserId: "user-1",
|
||||
ProjectId: "proj-1",
|
||||
ProjectDomainId: "domain-1",
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRoleAssignmentListInputSystem(t *testing.T) {
|
||||
input := api.RoleAssignmentsInput{}
|
||||
input.User.Id = "other-user"
|
||||
restrict, err := checkRoleAssignmentListInput(rbacscope.ScopeSystem, testRoleAssignmentCred(), &input)
|
||||
if err != nil {
|
||||
t.Fatalf("system scope: %v", err)
|
||||
}
|
||||
if restrict != "" {
|
||||
t.Fatalf("system restrictDomainId = %q", restrict)
|
||||
}
|
||||
if input.User.Id != "other-user" {
|
||||
t.Fatalf("system must not rewrite user id, got %q", input.User.Id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRoleAssignmentListInputUser(t *testing.T) {
|
||||
cred := testRoleAssignmentCred()
|
||||
input := api.RoleAssignmentsInput{}
|
||||
restrict, err := checkRoleAssignmentListInput(rbacscope.ScopeUser, cred, &input)
|
||||
if err != nil {
|
||||
t.Fatalf("user scope: %v", err)
|
||||
}
|
||||
if restrict != "" {
|
||||
t.Fatalf("user restrictDomainId = %q", restrict)
|
||||
}
|
||||
if input.User.Id != cred.UserId {
|
||||
t.Fatalf("user id = %q, want %q", input.User.Id, cred.UserId)
|
||||
}
|
||||
if input.IncludePolicies != nil {
|
||||
t.Fatal("user scope must not request include_policies")
|
||||
}
|
||||
|
||||
input.User.Id = "other-user"
|
||||
if _, err := checkRoleAssignmentListInput(rbacscope.ScopeUser, cred, &input); err == nil {
|
||||
t.Fatal("expected error when listing another user")
|
||||
}
|
||||
|
||||
input = api.RoleAssignmentsInput{}
|
||||
input.Group.Id = "group-1"
|
||||
if _, err := checkRoleAssignmentListInput(rbacscope.ScopeUser, cred, &input); err == nil {
|
||||
t.Fatal("expected error when listing by group")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRoleAssignmentListInputDomain(t *testing.T) {
|
||||
cred := testRoleAssignmentCred()
|
||||
input := api.RoleAssignmentsInput{}
|
||||
restrict, err := checkRoleAssignmentListInput(rbacscope.ScopeDomain, cred, &input)
|
||||
if err != nil {
|
||||
t.Fatalf("domain scope: %v", err)
|
||||
}
|
||||
if restrict != cred.ProjectDomainId {
|
||||
t.Fatalf("restrictDomainId = %q, want %q", restrict, cred.ProjectDomainId)
|
||||
}
|
||||
|
||||
input.ProjectDomainId = "other-domain"
|
||||
if _, err := checkRoleAssignmentListInput(rbacscope.ScopeDomain, cred, &input); err == nil {
|
||||
t.Fatal("expected error when listing another domain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRoleAssignmentListInputProject(t *testing.T) {
|
||||
cred := testRoleAssignmentCred()
|
||||
input := api.RoleAssignmentsInput{}
|
||||
restrict, err := checkRoleAssignmentListInput(rbacscope.ScopeProject, cred, &input)
|
||||
if err != nil {
|
||||
t.Fatalf("project scope: %v", err)
|
||||
}
|
||||
if restrict != "" {
|
||||
t.Fatalf("project restrictDomainId = %q", restrict)
|
||||
}
|
||||
if input.Scope.Project.Id != cred.ProjectId {
|
||||
t.Fatalf("project id = %q, want %q", input.Scope.Project.Id, cred.ProjectId)
|
||||
}
|
||||
|
||||
input.Scope.Project.Id = "other-proj"
|
||||
if _, err := checkRoleAssignmentListInput(rbacscope.ScopeProject, cred, &input); err == nil {
|
||||
t.Fatal("expected error when listing another project")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckRoleAssignmentListInputNone(t *testing.T) {
|
||||
input := api.RoleAssignmentsInput{}
|
||||
if _, err := checkRoleAssignmentListInput(rbacscope.ScopeNone, testRoleAssignmentCred(), &input); err == nil {
|
||||
t.Fatal("expected error when scope is none")
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,12 @@ var (
|
||||
Action: PolicyActionDelete,
|
||||
Result: rbacutils.Allow,
|
||||
},
|
||||
{
|
||||
Service: api.SERVICE_TYPE,
|
||||
Resource: "role_assignments",
|
||||
Action: PolicyActionList,
|
||||
Result: rbacutils.Allow,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@ var (
|
||||
}
|
||||
identityUserResources = []string{
|
||||
"credentials",
|
||||
"role_assignments",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user