mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-16 15:07:59 +08:00
fix(cloudid): samluser owner (#25864)
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/rbacscope"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
@@ -80,6 +81,44 @@ func (manager *SSamluserManager) GetIVirtualModelManager() db.IVirtualModelManag
|
||||
return manager.GetVirtualObject().(db.IVirtualModelManager)
|
||||
}
|
||||
|
||||
// ResourceScope 声明 samluser 归属本地用户。
|
||||
// 模型同时带 domain_id,若不覆盖,scope=user 会被提升为 domain,
|
||||
// 普通用户只有 user 范围的 list 权限,查询会 403。
|
||||
func (manager *SSamluserManager) ResourceScope() rbacscope.TRbacScope {
|
||||
return rbacscope.ScopeUser
|
||||
}
|
||||
|
||||
// effectiveSamluserListScope 在未显式指定 scope、且不是 admin=true 时,
|
||||
// 为域/系统读者保留原来的域范围默认视图。普通用户不改写,走 user scope。
|
||||
func effectiveSamluserListScope(reqScope string, isAdmin, domainAllow bool) (string, bool) {
|
||||
if reqScope == string(rbacscope.ScopeNone) {
|
||||
reqScope = ""
|
||||
}
|
||||
if len(reqScope) > 0 || isAdmin || !domainAllow {
|
||||
return "", false
|
||||
}
|
||||
return string(rbacscope.ScopeDomain), true
|
||||
}
|
||||
|
||||
// samluserListOwnerConstraint 限制普通用户只能看到自己的 samluser。
|
||||
// 域/系统读者保留请求里的 user 过滤。
|
||||
func samluserListOwnerConstraint(callerId string, domainAllow bool) (ownerId string, restrict bool) {
|
||||
if domainAllow {
|
||||
return "", false
|
||||
}
|
||||
return callerId, true
|
||||
}
|
||||
|
||||
func (manager *SSamluserManager) ValidateListConditions(ctx context.Context, userCred mcclient.TokenCredential, query *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
reqScope, _ := query.GetString("scope")
|
||||
isAdmin := query.Contains("admin") && jsonutils.QueryBoolean(query, "admin", false)
|
||||
domainAllow := db.IsDomainAllowList(userCred, manager).Result.IsAllow()
|
||||
if scope, ok := effectiveSamluserListScope(reqScope, isAdmin, domainAllow); ok {
|
||||
query.Set("scope", jsonutils.NewString(scope))
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func (manager *SSamluserManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
|
||||
groupId, _ := data.GetString("cloudgroup_id")
|
||||
return jsonutils.Marshal(map[string]string{"cloudgroup_id": groupId})
|
||||
@@ -96,10 +135,20 @@ func (manager *SSamluserManager) FilterByUniqValues(q *sqlchemy.SQuery, values j
|
||||
// SAML认证用户列表
|
||||
func (manager *SSamluserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.SamluserListInput) (*sqlchemy.SQuery, error) {
|
||||
var err error
|
||||
domainAllow := db.IsDomainAllowList(userCred, manager).Result.IsAllow()
|
||||
ownerId, restrict := samluserListOwnerConstraint(userCred.GetUserId(), domainAllow)
|
||||
if restrict {
|
||||
// 丢弃 user/user_id,避免基类按请求参数查到其他用户。
|
||||
query.UserId = ""
|
||||
query.User = ""
|
||||
}
|
||||
q, err = manager.SStatusDomainLevelUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusDomainLevelUserResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if restrict {
|
||||
q = q.Equals("owner_id", ownerId)
|
||||
}
|
||||
q, err = manager.SCloudgroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudgroupResourceListInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -168,6 +217,9 @@ func (manager *SSamluserManager) ValidateCreateData(
|
||||
return input, errors.Wrapf(err, "FetchUserById")
|
||||
}
|
||||
input.OwnerId = user.Id
|
||||
if input.OwnerId != userCred.GetUserId() && !db.IsDomainAllowCreate(userCred, manager).Result.IsAllow() {
|
||||
return input, httperrors.NewForbiddenError("not allow to create samluser for another user")
|
||||
}
|
||||
if len(input.Name) == 0 {
|
||||
input.Name = user.Name
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
func TestSamluserResourceScope(t *testing.T) {
|
||||
if got := SamluserManager.ResourceScope(); got != rbacscope.ScopeUser {
|
||||
t.Fatalf("ResourceScope = %s, want %s", got, rbacscope.ScopeUser)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveSamluserListScope(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
reqScope string
|
||||
isAdmin bool
|
||||
domainAllow bool
|
||||
wantScope string
|
||||
wantSet bool
|
||||
}{
|
||||
{name: "domain reader keeps domain default", reqScope: "", domainAllow: true, wantScope: string(rbacscope.ScopeDomain), wantSet: true},
|
||||
{name: "system reader keeps domain default", reqScope: "", domainAllow: true, wantScope: string(rbacscope.ScopeDomain), wantSet: true},
|
||||
{name: "ordinary user keeps user scope", reqScope: "", domainAllow: false, wantSet: false},
|
||||
{name: "explicit user scope stays", reqScope: "user", domainAllow: false, wantSet: false},
|
||||
{name: "explicit domain scope stays for rbac to reject", reqScope: "domain", domainAllow: false, wantSet: false},
|
||||
{name: "admin=true is not rewritten", reqScope: "", isAdmin: true, domainAllow: true, wantSet: false},
|
||||
{name: "none scope uses domain default for domain reader", reqScope: "none", domainAllow: true, wantScope: string(rbacscope.ScopeDomain), wantSet: true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, ok := effectiveSamluserListScope(c.reqScope, c.isAdmin, c.domainAllow)
|
||||
if ok != c.wantSet || got != c.wantScope {
|
||||
t.Fatalf("effectiveSamluserListScope(%q, %v, %v) = (%q, %v), want (%q, %v)", c.reqScope, c.isAdmin, c.domainAllow, got, ok, c.wantScope, c.wantSet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSamluserListOwnerConstraint(t *testing.T) {
|
||||
owner, restrict := samluserListOwnerConstraint("user-a", false)
|
||||
if !restrict || owner != "user-a" {
|
||||
t.Fatalf("ordinary user constraint = (%q, %v), want (user-a, true)", owner, restrict)
|
||||
}
|
||||
owner, restrict = samluserListOwnerConstraint("admin", true)
|
||||
if restrict || owner != "" {
|
||||
t.Fatalf("domain reader constraint = (%q, %v), want (\"\", false)", owner, restrict)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user