fix: api horizontal privilege (#2434)

This commit is contained in:
junwen-lee
2025-11-05 20:52:07 +08:00
committed by GitHub
parent 73ec2c3103
commit 5d1276b2b8
51 changed files with 2070 additions and 222 deletions

View File

@ -72,6 +72,8 @@ import (
"github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/knowledgemock"
knowledge "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
"github.com/coze-dev/coze-studio/backend/crossdomain/message/messagemock"
crosspermission "github.com/coze-dev/coze-studio/backend/crossdomain/permission"
"github.com/coze-dev/coze-studio/backend/crossdomain/permission/permissionmock"
crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/plugin"
pluginImpl "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/impl"
pluginmodel "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/model"
@ -81,6 +83,7 @@ import (
conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
entity4 "github.com/coze-dev/coze-studio/backend/domain/memory/database/entity"
entity2 "github.com/coze-dev/coze-studio/backend/domain/openauth/openapiauth/entity"
permission "github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
entity3 "github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
entity5 "github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
@ -181,6 +184,11 @@ func newWfTestRunner(t *testing.T) *wfTestRunner {
ctxcache.Store(c, consts.SessionDataKeyInCtx, &userentity.Session{
UserID: 123,
})
// Add API auth info for OpenAPI endpoints
ctxcache.Store(c, consts.OpenapiAuthKeyInCtx, &entity2.ApiKey{
UserID: 123,
ConnectorID: consts.APIConnectorID,
})
ctx.Next(c)
})
h.POST("/api/workflow_api/node_template_list", NodeTemplateList)
@ -330,6 +338,11 @@ func newWfTestRunner(t *testing.T) *wfTestRunner {
mockAgentRun := agentrunmock.NewMockAgentRun(ctrl)
crossagentrun.SetDefaultSVC(mockAgentRun)
// Initialize permission service for tests
mockPermission := permissionmock.NewMockPermission(ctrl)
mockPermission.EXPECT().CheckAuthz(gomock.Any(), gomock.Any()).Return(&permission.CheckAuthzResult{Decision: permission.Allow}, nil).AnyTimes()
crosspermission.SetDefaultSVC(mockPermission)
mockey.Mock((*user.UserApplicationService).MGetUserBasicInfo).Return(&playground.MGetUserBasicInfoResponse{
UserBasicInfoMap: make(map[string]*playground.UserBasicInfo),
}, nil).Build()
@ -1029,6 +1042,8 @@ func (r *wfTestRunner) openapiStream(id string, input any) *sse.Reader {
hReq.SetMethod("POST")
hReq.SetBody(m)
hReq.SetHeader("Content-Type", "application/json")
// Add Authorization header for API authentication
hReq.SetHeader("Authorization", "Bearer test-api-key-123")
err = c.Do(context.Background(), hReq, hResp)
assert.NoError(r.t, err)
@ -1059,6 +1074,8 @@ func (r *wfTestRunner) openapiResume(id string, eventID string, resumeData strin
hReq.SetMethod("POST")
hReq.SetBody(m)
hReq.SetHeader("Content-Type", "application/json")
// Add Authorization header for API authentication
hReq.SetHeader("Authorization", "Bearer test-api-key-123")
err = c.Do(context.Background(), hReq, hResp)
assert.NoError(r.t, err)

View File

@ -45,6 +45,7 @@ import (
"github.com/coze-dev/coze-studio/backend/application/workflow"
"github.com/coze-dev/coze-studio/backend/bizpkg/config"
connectorModel "github.com/coze-dev/coze-studio/backend/crossdomain/connector/model"
knowledgeModel "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
pluginConsts "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/consts"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
@ -52,6 +53,7 @@ import (
"github.com/coze-dev/coze-studio/backend/domain/app/service"
connector "github.com/coze-dev/coze-studio/backend/domain/connector/service"
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
"github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
search "github.com/coze-dev/coze-studio/backend/domain/search/service"
@ -389,7 +391,37 @@ func (a *APPApplicationService) getLatestPublishRecord(ctx context.Context, appI
return latestRecord, nil
}
func checkUserSpace(ctx context.Context, uid int64, spaceID int64) error {
// Use permission service to check workspace access
result, err := permission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
ResourceIdentifier: []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeWorkspace,
ID: []int64{spaceID},
Action: permission.ActionRead,
},
},
OperatorID: uid,
})
if err != nil {
return fmt.Errorf("failed to check workspace permission: %w", err)
}
if result.Decision != permission.Allow {
return fmt.Errorf("user %d does not have access to space %d", uid, spaceID)
}
return nil
}
func (a *APPApplicationService) ReportUserBehavior(ctx context.Context, req *playground.ReportUserBehaviorRequest) (resp *playground.ReportUserBehaviorResponse, err error) {
uid := ctxutil.MustGetUIDFromCtx(ctx)
err = checkUserSpace(ctx, uid, req.GetSpaceID())
if err != nil {
return nil, err
}
err = a.projectEventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Updated,
Project: &searchEntity.ProjectDocument{

View File

@ -20,6 +20,8 @@ import (
"context"
"fmt"
"github.com/coze-dev/coze-studio/backend/application/permission"
"github.com/coze-dev/coze-studio/backend/application/app"
"github.com/coze-dev/coze-studio/backend/application/base/appinfra"
"github.com/coze-dev/coze-studio/backend/application/connector"
@ -41,6 +43,8 @@ import (
singleagentImpl "github.com/coze-dev/coze-studio/backend/crossdomain/agent/impl"
crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/agentrun"
agentrunImpl "github.com/coze-dev/coze-studio/backend/crossdomain/agentrun/impl"
crossapp "github.com/coze-dev/coze-studio/backend/crossdomain/app"
appImpl "github.com/coze-dev/coze-studio/backend/crossdomain/app/impl"
crossconnector "github.com/coze-dev/coze-studio/backend/crossdomain/connector"
connectorImpl "github.com/coze-dev/coze-studio/backend/crossdomain/connector/impl"
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/conversation"
@ -53,6 +57,8 @@ import (
knowledgeImpl "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/impl"
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/message"
messageImpl "github.com/coze-dev/coze-studio/backend/crossdomain/message/impl"
crosspermission "github.com/coze-dev/coze-studio/backend/crossdomain/permission"
permissionImpl "github.com/coze-dev/coze-studio/backend/crossdomain/permission/impl"
crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/plugin"
pluginImpl "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/impl"
crosssearch "github.com/coze-dev/coze-studio/backend/crossdomain/search"
@ -90,6 +96,8 @@ type basicServices struct {
templateSVC *template.ApplicationService
openAuthSVC *openauth.OpenAuthApplicationService
uploadSVC *upload.UploadService
permissionSVC *permission.PermissionApplicationService
}
type primaryServices struct {
@ -101,6 +109,7 @@ type primaryServices struct {
knowledgeSVC *knowledge.KnowledgeApplicationService
workflowSVC *workflow.ApplicationService
shortcutSVC *shortcutcmd.ShortcutCmdApplicationService
appSVC *app.APPApplicationService
}
type complexServices struct {
@ -138,6 +147,9 @@ func Init(ctx context.Context) (err error) {
return fmt.Errorf("Init - initVitalServices failed, err: %v", err)
}
// Initialize permission service first as it's required by other services
crosspermission.SetDefaultSVC(permissionImpl.InitDomainService(basicServices.permissionSVC.DomainSVC))
crossconnector.SetDefaultSVC(connectorImpl.InitDomainService(basicServices.connectorSVC.DomainSVC))
crossdatabase.SetDefaultSVC(databaseImpl.InitDomainService(primaryServices.memorySVC.DatabaseDomainSVC))
crossknowledge.SetDefaultSVC(knowledgeImpl.InitDomainService(primaryServices.knowledgeSVC.DomainSVC))
@ -153,6 +165,8 @@ func Init(ctx context.Context) (err error) {
crosssearch.SetDefaultSVC(searchImpl.InitDomainService(complexServices.searchSVC.DomainSVC))
crossupload.SetDefaultSVC(uploadImpl.InitDomainService(basicServices.uploadSVC.UploadSVC))
crossapp.SetDefaultSVC(appImpl.InitDomainService(complexServices.appSVC.DomainSVC))
return nil
}
@ -179,6 +193,8 @@ func initBasicServices(ctx context.Context, infra *appinfra.AppDependencies, e *
Storage: infra.OSS,
})
permissionSVC := permission.InitService(&permission.ServiceComponents{})
return &basicServices{
infra: infra,
eventbus: e,
@ -189,6 +205,8 @@ func initBasicServices(ctx context.Context, infra *appinfra.AppDependencies, e *
templateSVC: templateSVC,
openAuthSVC: openAuthSVC,
uploadSVC: uploadSVC,
permissionSVC: permissionSVC,
}, nil
}

View File

@ -91,6 +91,9 @@ func (c *ConversationApplicationService) Run(ctx context.Context, sseSender *sse
if err != nil {
return err
}
if cmdMeta.ObjectID > 0 && cmdMeta.ObjectID != agentInfo.AgentID {
return errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "agent not match"))
}
shortcutCmd = cmdMeta
}

View File

@ -322,8 +322,24 @@ func (c *ConversationApplicationService) DeleteMessage(ctx context.Context, mr *
func (c *ConversationApplicationService) BreakMessage(ctx context.Context, mr *message.BreakMessageRequest) (*message.BreakMessageResponse, error) {
resp := new(message.BreakMessageResponse)
messageInfo, err := c.MessageDomainSVC.GetByID(ctx, mr.GetAnswerMessageID())
if err != nil {
return resp, err
}
if messageInfo == nil {
return resp, errorx.New(errno.ErrConversationMessageNotFound)
}
err := c.MessageDomainSVC.Broken(ctx, &entity.BrokenMeta{
userID := ctxutil.GetUIDFromCtx(ctx)
if messageInfo.UserID != conv.Int64ToStr(*userID) {
return resp, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "permission denied"))
}
if messageInfo.ConversationID != mr.ConversationID {
return resp, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "conversation not match"))
}
err = c.MessageDomainSVC.Broken(ctx, &entity.BrokenMeta{
ID: *mr.AnswerMessageID,
Position: mr.BrokenPos,
})

View File

@ -63,12 +63,20 @@ func (a *OpenapiAgentRunApplication) OpenapiAgentRun(ctx context.Context, sseSen
return caErr
}
if creatorID != agentInfo.CreatorID {
return errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "agent not match"))
}
conversationData, ccErr := a.checkConversation(ctx, ar, creatorID, connectorID)
if ccErr != nil {
logs.CtxErrorf(ctx, "checkConversation err:%v", ccErr)
return ccErr
}
if conversationData.CreatorID != creatorID {
return errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "user not match"))
}
spaceID := agentInfo.SpaceID
arr, err := a.buildAgentRunRequest(ctx, ar, connectorID, spaceID, conversationData)
if err != nil {
@ -643,6 +651,10 @@ func (a *OpenapiAgentRunApplication) CancelRun(ctx context.Context, req *run.Can
return nil, err
}
if req.ConversationID != runRecord.ConversationID {
return nil, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "conversation not match"))
}
if userID != conversationData.CreatorID {
return nil, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "user not match"))
}

View File

@ -159,8 +159,9 @@ func TestOpenapiAgentRun_Success(t *testing.T) {
// Mock agent check
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -217,8 +218,9 @@ func TestOpenapiAgentRun_CheckConversationError(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -240,8 +242,9 @@ func TestOpenapiAgentRun_ConversationPermissionError(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -268,8 +271,9 @@ func TestOpenapiAgentRun_CreateNewConversation(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -305,7 +309,8 @@ func TestOpenapiAgentRun_AgentRunError(t *testing.T) {
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -339,8 +344,9 @@ func TestOpenapiAgentRun_WithShortcutCommand(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -379,8 +385,9 @@ func TestOpenapiAgentRun_WithMultipleMessages(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -421,8 +428,9 @@ func TestOpenapiAgentRun_WithAssistantMessage(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -490,8 +498,9 @@ func TestOpenapiAgentRun_WithMixedContentTypes(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -554,8 +563,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidRole(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -597,8 +607,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_InvalidType(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -640,8 +651,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_AnswerWithNonTextContent(t *tes
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -681,8 +693,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileURL(t *testing.T)
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -725,8 +738,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_MixApiWithFileID(t *testing.T)
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -780,8 +794,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_FileIDError(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -829,8 +844,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_EmptyContent(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)
@ -881,8 +897,9 @@ func TestOpenapiAgentRun_ParseAdditionalMessages_NilMessage(t *testing.T) {
// Mock agent check success
mockAgent := &saEntity.SingleAgent{
SingleAgent: &singleagent.SingleAgent{
AgentID: 67890,
SpaceID: 54321,
AgentID: 67890,
SpaceID: 54321,
CreatorID: 12345,
},
}
mockSingleAgent.EXPECT().ObtainAgentByIdentity(ctx, gomock.Any()).Return(mockAgent, nil)

View File

@ -32,9 +32,11 @@ import (
resource "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/application/search"
model "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
"github.com/coze-dev/coze-studio/backend/domain/knowledge/entity"
"github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
"github.com/coze-dev/coze-studio/backend/domain/permission"
resourceEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
cd "github.com/coze-dev/coze-studio/backend/infra/document"
"github.com/coze-dev/coze-studio/backend/infra/document/parser"
@ -65,6 +67,12 @@ func (k *KnowledgeApplicationService) CreateKnowledge(ctx context.Context, req *
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, ptr.Of(req.SpaceID), nil, nil, nil)
if err != nil {
return dataset.NewCreateDatasetResponse(), err
}
createReq := service.CreateKnowledgeRequest{
Name: req.Name,
Description: req.Description,
@ -115,6 +123,16 @@ func (k *KnowledgeApplicationService) DatasetDetail(ctx context.Context, req *da
var err error
var datasetIDs []int64
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err = k.checkPermission(ctx, uid, ptr.Of(req.SpaceID), nil, nil, nil)
if err != nil {
return dataset.NewDatasetDetailResponse(), err
}
datasetIDs, err = slices.TransformWithErrorCheck(req.GetDatasetIDs(), func(s string) (int64, error) {
id, err := strconv.ParseInt(s, 10, 64)
return id, err
@ -146,6 +164,12 @@ func (k *KnowledgeApplicationService) DatasetDetail(ctx context.Context, req *da
}
func (k *KnowledgeApplicationService) ListKnowledge(ctx context.Context, req *dataset.ListDatasetRequest) (*dataset.ListDatasetResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
var err error
var projectID int64
request := service.ListKnowledgeRequest{}
@ -178,6 +202,12 @@ func (k *KnowledgeApplicationService) ListKnowledge(ctx context.Context, req *da
}
if req.GetSpaceID() != 0 {
request.SpaceID = &req.SpaceID
err = k.checkPermission(ctx, uid, ptr.Of(req.SpaceID), nil, nil, nil)
if err != nil {
return dataset.NewListDatasetResponse(), err
}
}
request.OrderType = &orderType
@ -219,7 +249,18 @@ func (k *KnowledgeApplicationService) ListKnowledge(ctx context.Context, req *da
}
func (k *KnowledgeApplicationService) DeleteKnowledge(ctx context.Context, req *dataset.DeleteDatasetRequest) (*dataset.DeleteDatasetResponse, error) {
err := k.DomainSVC.DeleteKnowledge(ctx, &service.DeleteKnowledgeRequest{
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
err = k.DomainSVC.DeleteKnowledge(ctx, &service.DeleteKnowledgeRequest{
KnowledgeID: req.GetDatasetID(),
})
if err != nil {
@ -241,6 +282,17 @@ func (k *KnowledgeApplicationService) DeleteKnowledge(ctx context.Context, req *
}
func (k *KnowledgeApplicationService) UpdateKnowledge(ctx context.Context, req *dataset.UpdateDatasetRequest) (*dataset.UpdateDatasetResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
now := time.Now().UnixMilli()
updateReq := service.UpdateKnowledgeRequest{
KnowledgeID: req.GetDatasetID(),
@ -253,7 +305,7 @@ func (k *KnowledgeApplicationService) UpdateKnowledge(ctx context.Context, req *
if req.Status != nil {
updateReq.Status = ptr.Of(convertDatasetStatus2Entity(req.GetStatus()))
}
err := k.DomainSVC.UpdateKnowledge(ctx, &updateReq)
err = k.DomainSVC.UpdateKnowledge(ctx, &updateReq)
if err != nil {
logs.CtxErrorf(ctx, "update knowledge failed, err: %v", err)
return dataset.NewUpdateDatasetResponse(), err
@ -288,6 +340,11 @@ func (k *KnowledgeApplicationService) CreateDocument(ctx context.Context, req *d
return dataset.NewCreateDocumentResponse(), errors.New("knowledge not found")
}
knowledgeInfo := listResp.KnowledgeList[0]
if knowledgeInfo.CreatorID != *uid {
return dataset.NewCreateDocumentResponse(), errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
documents := []*entity.Document{}
if len(req.GetDocumentBases()) == 0 {
return dataset.NewCreateDocumentResponse(), errors.New("document base is empty")
@ -345,6 +402,12 @@ func (k *KnowledgeApplicationService) CreateDocument(ctx context.Context, req *d
}
func (k *KnowledgeApplicationService) ListDocument(ctx context.Context, req *dataset.ListDocumentRequest) (*dataset.ListDocumentResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
var limit int = int(req.GetSize())
var offset int = int(req.GetPage() * req.GetSize())
var err error
@ -374,6 +437,9 @@ func (k *KnowledgeApplicationService) ListDocument(ctx context.Context, req *dat
resp.Total = int32(listResp.Total)
resp.DocumentInfos = make([]*dataset.DocumentInfo, 0)
for i := range documents {
if documents[i].CreatorID != *uid {
return dataset.NewListDocumentResponse(), errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
resp.DocumentInfos = append(resp.DocumentInfos, convertDocument2Model(documents[i]))
}
return resp, nil
@ -383,6 +449,26 @@ func (k *KnowledgeApplicationService) DeleteDocument(ctx context.Context, req *d
if len(req.GetDocumentIds()) == 0 {
return dataset.NewDeleteDocumentResponse(), errors.New("document ids is empty")
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
transformedIDs, err := slices.TransformWithErrorCheck(req.GetDocumentIds(), func(s string) (int64, error) {
id, err := strconv.ParseInt(s, 10, 64)
return id, err
})
if err != nil {
logs.CtxErrorf(ctx, "convert string ids failed, err: %v", err)
return dataset.NewDeleteDocumentResponse(), err
}
err = k.checkPermission(ctx, uid, nil, transformedIDs, nil, nil)
if err != nil {
return dataset.NewDeleteDocumentResponse(), err
}
for i := range req.GetDocumentIds() {
docID, err := strconv.ParseInt(req.GetDocumentIds()[i], 10, 64)
if err != nil {
@ -401,7 +487,17 @@ func (k *KnowledgeApplicationService) DeleteDocument(ctx context.Context, req *d
}
func (k *KnowledgeApplicationService) UpdateDocument(ctx context.Context, req *dataset.UpdateDocumentRequest) (*dataset.UpdateDocumentResponse, error) {
err := k.DomainSVC.UpdateDocument(ctx, &service.UpdateDocumentRequest{
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, []int64{req.GetDocumentID()}, nil, nil)
if err != nil {
return dataset.NewUpdateDocumentResponse(), err
}
err = k.DomainSVC.UpdateDocument(ctx, &service.UpdateDocumentRequest{
DocumentID: req.GetDocumentID(),
DocumentName: req.DocumentName,
TableInfo: &entity.TableInfo{
@ -415,7 +511,66 @@ func (k *KnowledgeApplicationService) UpdateDocument(ctx context.Context, req *d
return &dataset.UpdateDocumentResponse{}, nil
}
func (k *KnowledgeApplicationService) checkPermission(ctx context.Context, uid *int64, spaceID *int64, documentIDs []int64, knowledgeID *int64, sliceIDs []int64) error {
rd := []*permission.ResourceIdentifier{}
if spaceID != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeWorkspace,
ID: []int64{*spaceID},
Action: permission.ActionRead,
})
}
if documentIDs != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeKnowledgeDocument,
ID: documentIDs,
Action: permission.ActionRead,
})
}
if sliceIDs != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeKnowledgeSlice,
ID: sliceIDs,
Action: permission.ActionRead,
})
}
if knowledgeID != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeKnowledge,
ID: []int64{*knowledgeID},
Action: permission.ActionRead,
})
}
if len(rd) == 0 {
return nil
}
checkResult, err := permission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
ResourceIdentifier: rd,
OperatorID: *uid,
})
if err != nil {
logs.CtxErrorf(ctx, "check authz failed, err: %v", err)
return err
}
if checkResult.Decision != permission.Allow {
return errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
return nil
}
func (k *KnowledgeApplicationService) GetDocumentProgress(ctx context.Context, req *dataset.GetDocumentProgressRequest) (*dataset.GetDocumentProgressResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
docIDs, err := slices.TransformWithErrorCheck(req.GetDocumentIds(), func(s string) (int64, error) {
id, err := strconv.ParseInt(s, 10, 64)
return id, err
@ -424,6 +579,11 @@ func (k *KnowledgeApplicationService) GetDocumentProgress(ctx context.Context, r
logs.CtxErrorf(ctx, "convert string ids failed, err: %v", err)
return dataset.NewGetDocumentProgressResponse(), err
}
err = k.checkPermission(ctx, uid, nil, docIDs, nil, nil)
if err != nil {
return dataset.NewGetDocumentProgressResponse(), err
}
domainResp, err := k.DomainSVC.MGetDocumentProgress(ctx, &service.MGetDocumentProgressRequest{
DocumentIDs: docIDs,
})
@ -450,6 +610,10 @@ func (k *KnowledgeApplicationService) GetDocumentProgress(ctx context.Context, r
}
func (k *KnowledgeApplicationService) Resegment(ctx context.Context, req *dataset.ResegmentRequest) (*dataset.ResegmentResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
resp := dataset.NewResegmentResponse()
resp.DocumentInfos = make([]*dataset.DocumentInfo, 0)
for i := range req.GetDocumentIds() {
@ -471,6 +635,10 @@ func (k *KnowledgeApplicationService) Resegment(ctx context.Context, req *datase
logs.CtxErrorf(ctx, "resegment document failed, err: %v", err)
return dataset.NewResegmentResponse(), err
}
if resegmentResp.Document.Info.CreatorID != *uid {
return dataset.NewResegmentResponse(), errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
resp.DocumentInfos = append(resp.DocumentInfos, &dataset.DocumentInfo{
Name: resegmentResp.Document.Name,
DocumentID: resegmentResp.Document.ID,
@ -494,6 +662,9 @@ func (k *KnowledgeApplicationService) CreateSlice(ctx context.Context, req *data
if len(listResp.Documents) != 1 {
return dataset.NewCreateSliceResponse(), errors.New("document not found")
}
if listResp.Documents[0].CreatorID != *uid {
return dataset.NewCreateSliceResponse(), errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
sliceEntity := &model.Slice{
Info: model.Info{
CreatorID: *uid,
@ -531,12 +702,28 @@ func (k *KnowledgeApplicationService) CreateSlice(ctx context.Context, req *data
}
func (k *KnowledgeApplicationService) DeleteSlice(ctx context.Context, req *dataset.DeleteSliceRequest) (*dataset.DeleteSliceResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
sliceIDs := make([]int64, 0, len(req.GetSliceIds()))
for i := range req.GetSliceIds() {
sliceID, err := strconv.ParseInt(req.GetSliceIds()[i], 10, 64)
if err != nil {
logs.CtxErrorf(ctx, "parse int failed, err: %v", err)
return dataset.NewDeleteSliceResponse(), err
}
sliceIDs = append(sliceIDs, sliceID)
}
err := k.checkPermission(ctx, uid, nil, nil, nil, sliceIDs)
if err != nil {
logs.CtxErrorf(ctx, "check permission failed, err: %v", err)
return dataset.NewDeleteSliceResponse(), err
}
for i := range sliceIDs {
sliceID := sliceIDs[i]
err = k.DomainSVC.DeleteSlice(ctx, &service.DeleteSliceRequest{
SliceID: sliceID,
})
@ -559,6 +746,10 @@ func (k *KnowledgeApplicationService) UpdateSlice(ctx context.Context, req *data
if err != nil {
return nil, errorx.New(errno.ErrKnowledgeInvalidParamCode, errorx.KV("msg", "slice not found"))
}
if getSliceResp.Slice != nil && getSliceResp.Slice.Info.CreatorID != *uid {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "permission denied"))
}
docID := getSliceResp.Slice.DocumentID
listResp, err := k.DomainSVC.ListDocument(ctx, &service.ListDocumentRequest{
@ -646,6 +837,19 @@ func packTableSliceColumnData(ctx context.Context, slice *model.Slice, text stri
}
func (k *KnowledgeApplicationService) ListSlice(ctx context.Context, req *dataset.ListSliceRequest) (*dataset.ListSliceResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
if req.DatasetID != nil {
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(*req.DatasetID), nil)
if err != nil {
return nil, err
}
}
listResp, err := k.DomainSVC.ListSlice(ctx, &service.ListSliceRequest{
KnowledgeID: req.DatasetID,
DocumentID: req.DocumentID,
@ -684,6 +888,17 @@ func (k *KnowledgeApplicationService) GetTableSchema(ctx context.Context, req *d
domainResp *service.TableSchemaResponse
err error
)
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
if req.GetDocumentID() > 0 {
err = k.checkPermission(ctx, uid, nil, []int64{req.GetDocumentID()}, nil, nil)
if err != nil {
return nil, err
}
}
if req.SourceFile == nil { // alter table
domainResp, err = k.DomainSVC.GetAlterTableSchema(ctx, &service.AlterTableSchemaRequest{
@ -751,6 +966,18 @@ func (k *KnowledgeApplicationService) ValidateTableSchema(ctx context.Context, r
if srcInfo == nil {
return nil, fmt.Errorf("source info not provided")
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
if req.GetDocumentID() > 0 {
err = k.checkPermission(ctx, uid, nil, []int64{req.GetDocumentID()}, nil, nil)
if err != nil {
return nil, err
}
}
var tableSheet *entity.TableSheet
if req.TableSheet != nil {
tableSheet = &entity.TableSheet{
@ -773,6 +1000,18 @@ func (k *KnowledgeApplicationService) ValidateTableSchema(ctx context.Context, r
}
func (k *KnowledgeApplicationService) GetDocumentTableInfo(ctx context.Context, req *document.GetDocumentTableInfoRequest) (*document.GetDocumentTableInfoResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
if req.GetDocumentID() > 0 {
err := k.checkPermission(ctx, uid, nil, []int64{req.GetDocumentID()}, nil, nil)
if err != nil {
return nil, err
}
}
domainResp, err := k.DomainSVC.GetDocumentTableInfo(ctx, &service.GetDocumentTableInfoRequest{
DocumentID: req.DocumentID,
SourceInfo: &service.TableSourceInfo{
@ -804,6 +1043,13 @@ func (k *KnowledgeApplicationService) CreateDocumentReview(ctx context.Context,
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
if req.GetDatasetID() > 0 {
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
}
createResp, err := k.DomainSVC.CreateDocumentReview(ctx, convertCreateDocReviewReq(req))
if err != nil {
logs.CtxErrorf(ctx, "create document review failed, err: %v", err)
@ -838,6 +1084,12 @@ func (k *KnowledgeApplicationService) MGetDocumentReview(ctx context.Context, re
logs.CtxErrorf(ctx, "parse int failed, err: %v", err)
return dataset.NewMGetDocumentReviewResponse(), err
}
err = k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
mGetResp, err := k.DomainSVC.MGetDocumentReview(ctx, &service.MGetDocumentReviewRequest{
KnowledgeID: req.GetDatasetID(),
ReviewIDs: reviewIDs,
@ -867,7 +1119,12 @@ func (k *KnowledgeApplicationService) SaveDocumentReview(ctx context.Context, re
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.DomainSVC.SaveDocumentReview(ctx, &service.SaveDocumentReviewRequest{
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
err = k.DomainSVC.SaveDocumentReview(ctx, &service.SaveDocumentReviewRequest{
KnowledgeID: req.GetDatasetID(),
DocTreeJson: req.GetDocTreeJSON(),
ReviewID: req.GetReviewID(),
@ -953,6 +1210,12 @@ func (k *KnowledgeApplicationService) UpdatePhotoCaption(ctx context.Context, re
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, []int64{req.DocumentID}, nil, nil)
if err != nil {
return nil, err
}
resp := dataset.NewUpdatePhotoCaptionResponse()
listResp, err := k.DomainSVC.ListSlice(ctx, &service.ListSliceRequest{DocumentID: ptr.Of(req.DocumentID)})
if err != nil {
@ -999,8 +1262,17 @@ func (k *KnowledgeApplicationService) MoveKnowledgeToLibrary(ctx context.Context
return nil
}
func (k *KnowledgeApplicationService) ListPhoto(ctx context.Context, req *dataset.ListPhotoRequest) (*dataset.ListPhotoResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
resp := dataset.NewListPhotoResponse()
var err error
var offset int
if req.GetPage() >= 1 {
offset = int(req.GetSize() * (req.GetPage() - 1))
@ -1071,6 +1343,17 @@ func (k *KnowledgeApplicationService) PhotoDetail(ctx context.Context, req *data
resp.Msg = "document ids is empty"
return resp, nil
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, nil, ptr.Of(req.GetDatasetID()), nil)
if err != nil {
return nil, err
}
docIDs, err := slices.TransformWithErrorCheck(req.GetDocumentIds(), func(s string) (int64, error) {
id, err := strconv.ParseInt(s, 10, 64)
return id, err
@ -1084,15 +1367,12 @@ func (k *KnowledgeApplicationService) PhotoDetail(ctx context.Context, req *data
logs.CtxErrorf(ctx, "list photo slice failed, err: %v", err)
return resp, err
}
listDocResp, err := k.DomainSVC.ListDocument(ctx, &service.ListDocumentRequest{DocumentIDs: docIDs, SelectAll: true})
if err != nil {
logs.CtxErrorf(ctx, "get documents by slice ids failed, err: %v", err)
return resp, err
}
listDocResp, err := k.DomainSVC.ListDocument(ctx, &service.ListDocumentRequest{DocumentIDs: docIDs, SelectAll: true, KnowledgeID: req.GetDatasetID()})
if err != nil {
logs.CtxErrorf(ctx, "get documents by slice ids failed, err: %v", err)
return resp, err
}
photos := k.packPhotoInfo(listResp.Slices, listDocResp.Documents)
sort.SliceStable(photos, func(i, j int) bool {
return photos[i].UpdateTime > photos[j].UpdateTime
@ -1110,6 +1390,16 @@ func (k *KnowledgeApplicationService) ExtractPhotoCaption(ctx context.Context, r
resp.Msg = "document id is empty"
return resp, nil
}
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))
}
err := k.checkPermission(ctx, uid, nil, []int64{req.GetDocumentID()}, nil, nil)
if err != nil {
return nil, err
}
extractResp, err := k.DomainSVC.ExtractPhotoCaption(ctx, &service.ExtractPhotoCaptionRequest{DocumentID: req.GetDocumentID()})
if err != nil {
return resp, err

View File

@ -103,6 +103,11 @@ func (d *DatabaseApplicationService) ListDatabase(ctx context.Context, req *tabl
}
func (d *DatabaseApplicationService) GetDatabaseByID(ctx context.Context, req *table.SingleDatabaseRequest) (*table.SingleDatabaseResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "session required"))
}
basics := make([]*model.DatabaseBasic, 1)
b := &model.DatabaseBasic{
ID: req.ID,
@ -127,6 +132,10 @@ func (d *DatabaseApplicationService) GetDatabaseByID(ctx context.Context, req *t
return nil, fmt.Errorf("database %d not found", req.GetID())
}
if res.Databases[0].CreatorID != *uid {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "creator id is invalid"))
}
return ConvertDatabaseRes(res.Databases[0]), nil
}
@ -353,6 +362,11 @@ func (d *DatabaseApplicationService) UpdateDatabaseRecords(ctx context.Context,
}
func (d *DatabaseApplicationService) GetOnlineDatabaseId(ctx context.Context, req *table.GetOnlineDatabaseIdRequest) (*table.GetOnlineDatabaseIdResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "session required"))
}
basics := make([]*model.DatabaseBasic, 1)
basics[0] = &model.DatabaseBasic{
ID: req.ID,
@ -370,6 +384,10 @@ func (d *DatabaseApplicationService) GetOnlineDatabaseId(ctx context.Context, re
return nil, fmt.Errorf("database %d not found", req.ID)
}
if res.Databases[0].CreatorID != *uid {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "creator id is invalid"))
}
return &table.GetOnlineDatabaseIdResponse{
ID: res.Databases[0].OnlineID,
@ -544,6 +562,12 @@ func (d *DatabaseApplicationService) GetConnectorName(ctx context.Context, req *
}
func (d *DatabaseApplicationService) GetBotDatabase(ctx context.Context, req *table.GetBotTableRequest) (*table.GetBotTableResponse, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "session required"))
}
relationResp, err := d.DomainSVC.MGetRelationsByAgentID(ctx, &database.MGetRelationsByAgentIDRequest{
AgentID: req.GetBotID(),
TableType: req.GetTableType(),
@ -564,6 +588,11 @@ func (d *DatabaseApplicationService) GetBotDatabase(ctx context.Context, req *ta
if err != nil {
return nil, err
}
for _, db := range resp.Databases {
if db.CreatorID != *uid {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "creator id is invalid"))
}
}
return &table.GetBotTableResponse{
BotTableList: convertToBotTableList(resp.Databases, req.GetBotID(), relationMap),
@ -650,6 +679,11 @@ func (d *DatabaseApplicationService) GetDatabaseTableSchema(ctx context.Context,
tableType = req.GetTableDataType()
}
err := d.ValidateAccess(ctx, req.GetDatabaseID(), table.TableType_OnlineTable)
if err != nil {
return nil, err
}
schema, err := d.DomainSVC.GetDatabaseTableSchema(ctx, &database.GetDatabaseTableSchemaRequest{
DatabaseID: req.GetDatabaseID(),
UserID: *uid,

View File

@ -26,9 +26,11 @@ import (
"github.com/coze-dev/coze-studio/backend/api/model/data/variable/kvmemory"
"github.com/coze-dev/coze-studio/backend/api/model/data/variable/project_memory"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
crosspermission "github.com/coze-dev/coze-studio/backend/crossdomain/permission"
model "github.com/coze-dev/coze-studio/backend/crossdomain/variables/model"
"github.com/coze-dev/coze-studio/backend/domain/memory/variables/entity"
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
"github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/i18n"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
@ -213,7 +215,28 @@ func (v *VariableApplicationService) UpdateProjectVariable(ctx context.Context,
req.UserID = *uid
}
// TODO: project owner check
projectID, err := strconv.ParseInt(req.ProjectID, 10, 64)
if err != nil {
return nil, err
}
checkResult, err := crosspermission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
OperatorID: *uid,
ResourceIdentifier: []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeApp,
ID: []int64{projectID},
Action: permission.ActionRead,
},
},
})
if err != nil {
return nil, err
}
if checkResult.Decision != permission.Allow {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "no permission"))
}
sysVars := v.DomainSVC.GetSysVariableConf(ctx).ToVariables()
@ -259,7 +282,7 @@ func (v *VariableApplicationService) UpdateProjectVariable(ctx context.Context,
}
}
_, err := v.DomainSVC.UpsertProjectMeta(ctx, req.ProjectID, "", req.UserID, entity.NewVariables(list))
_, err = v.DomainSVC.UpsertProjectMeta(ctx, req.ProjectID, "", req.UserID, entity.NewVariables(list))
if err != nil {
return nil, err
}
@ -327,6 +350,15 @@ func (v *VariableApplicationService) GetPlayGroundMemory(ctx context.Context, re
connectId := ternary.IFElse(req.ConnectorID == nil, consts.CozeConnectorID, req.GetConnectorID())
connectorUID := ternary.IFElse(req.UserID == 0, *uid, req.UserID)
resourceID, err := strconv.ParseInt(bizID, 10, 64)
if err != nil {
return nil, err
}
err = v.checkPermission(ctx, uid, isProjectKV, resourceID, permission.ActionRead)
if err != nil {
return nil, err
}
e := entity.NewUserVariableMeta(&model.UserVariableMeta{
BizType: bizType,
BizID: bizID,
@ -345,6 +377,32 @@ func (v *VariableApplicationService) GetPlayGroundMemory(ctx context.Context, re
}, nil
}
func (v *VariableApplicationService) checkPermission(ctx context.Context, uid *int64, isProjectKV bool, resourceID int64, action permission.Action) error {
checkResult, err := crosspermission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
OperatorID: *uid,
ResourceIdentifier: []*permission.ResourceIdentifier{
{
Type: func() permission.ResourceType {
if isProjectKV {
return permission.ResourceTypeApp
}
return permission.ResourceTypeAgent
}(),
ID: []int64{resourceID},
Action: action,
},
},
})
if err != nil {
return err
}
if checkResult.Decision != permission.Allow {
return errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "no permission"))
}
return nil
}
func (v *VariableApplicationService) SetVariableInstance(ctx context.Context, req *kvmemory.SetKvMemoryReq) (*kvmemory.SetKvMemoryResp, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
@ -363,6 +421,15 @@ func (v *VariableApplicationService) SetVariableInstance(ctx context.Context, re
connectId := ternary.IFElse(req.ConnectorID == nil, consts.CozeConnectorID, req.GetConnectorID())
connectorUID := ternary.IFElse(req.GetUserID() == 0, *uid, req.GetUserID())
resourceID, err := strconv.ParseInt(bizID, 10, 64)
if err != nil {
return nil, err
}
err = v.checkPermission(ctx, uid, isProjectKV, resourceID, permission.ActionRead)
if err != nil {
return nil, err
}
e := entity.NewUserVariableMeta(&model.UserVariableMeta{
BizType: bizType,
BizID: bizID,

View File

@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 permission
import (
"github.com/coze-dev/coze-studio/backend/domain/permission"
)
type ServiceComponents struct {
}
type PermissionApplicationService struct {
DomainSVC permission.Permission
}
func InitService(components *ServiceComponents) *PermissionApplicationService {
domainSVC := permission.NewService()
return &PermissionApplicationService{
DomainSVC: domainSVC,
}
}

View File

@ -23,6 +23,7 @@ import (
pluginAPI "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop"
common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
resCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/crossdomain/plugin/model"
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
@ -30,6 +31,7 @@ import (
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"github.com/coze-dev/coze-studio/backend/types/errno"
)
func (p *PluginApplicationService) PublishPlugin(ctx context.Context, req *pluginAPI.PublishPluginRequest) (resp *pluginAPI.PublishPluginResponse, err error) {
@ -110,6 +112,12 @@ func (p *PluginApplicationService) GetPluginNextVersion(ctx context.Context, req
}
func (p *PluginApplicationService) GetDevPluginList(ctx context.Context, req *pluginAPI.GetDevPluginListRequest) (resp *pluginAPI.GetDevPluginListResponse, err error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
}
pageInfo := dto.PageInfo{
Name: req.Name,
Page: int(req.GetPage()),
@ -133,6 +141,10 @@ func (p *PluginApplicationService) GetDevPluginList(ctx context.Context, req *pl
pluginList := make([]*common.PluginInfoForPlayground, 0, len(res.Plugins))
for _, pl := range res.Plugins {
if pl.DeveloperID > 0 && pl.DeveloperID != *uid {
return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "plugin developer is not current user"))
}
tools, err := p.toolRepo.GetPluginAllDraftTools(ctx, pl.ID)
if err != nil {
return nil, errorx.Wrapf(err, "GetPluginAllDraftTools failed, pluginID=%d", pl.ID)

View File

@ -97,10 +97,19 @@ func (p *PromptApplicationService) UpsertPromptResource(ctx context.Context, req
func (p *PromptApplicationService) GetPromptResourceInfo(ctx context.Context, req *playground.GetPromptResourceInfoRequest) (
resp *playground.GetPromptResourceInfoResponse, err error,
) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrPromptPermissionCode, errorx.KV("msg", "no session data provided"))
}
promptInfo, err := p.DomainSVC.GetPromptResource(ctx, req.GetPromptResourceID())
if err != nil {
return nil, err
}
if promptInfo.CreatorID != *uid {
return nil, errorx.New(errno.ErrPromptPermissionCode, errorx.KV("msg", "no permission"))
}
return &playground.GetPromptResourceInfoResponse{
Data: promptInfoDo2To(promptInfo),

View File

@ -124,6 +124,9 @@ func (s *SearchApplicationService) LibraryResourceList(ctx context.Context, req
if res == nil {
continue
}
if res.CreatorID != nil && *res.CreatorID != *userID {
return nil, errorx.New(errno.ErrSearchPermissionCode, errorx.KV("msg", "user can't search resources created by themselves"))
}
filterResource = append(filterResource, res)
}
@ -276,6 +279,12 @@ func (s *SearchApplicationService) getAPPAllResources(ctx context.Context, appID
}
func (s *SearchApplicationService) packAPPResources(ctx context.Context, resources []*entity.ResourceDocument) ([]*common.ProjectResourceGroup, error) {
uid := ctxutil.GetUIDFromCtx(ctx)
if uid == nil {
return nil, errorx.New(errno.ErrSearchPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
}
workflowGroup := &common.ProjectResourceGroup{
GroupType: common.ProjectResourceGroupType_Workflow,
ResourceList: []*common.ProjectResourceInfo{},
@ -294,6 +303,10 @@ func (s *SearchApplicationService) packAPPResources(ctx context.Context, resourc
for idx := range resources {
v := resources[idx]
if v.OwnerID != nil && *v.OwnerID != *uid {
return nil, errorx.New(errno.ErrSearchPermissionCode, errorx.KV("msg", "user can't search resources created by others"))
}
tasks.Go(func() error {
ri, err := s.packProjectResource(ctx, v)
if err != nil {

View File

@ -18,10 +18,13 @@ package shortcutcmd
import (
"context"
"fmt"
"strconv"
"github.com/coze-dev/coze-studio/backend/api/model/playground"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
"github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/service"
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
@ -31,13 +34,54 @@ type ShortcutCmdApplicationService struct {
ShortCutDomainSVC service.ShortcutCmd
}
func checkPermission(ctx context.Context, uid int64, spaceID int64, workflowID int64) error {
// Use permission service to check workspace access
rd := []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeWorkspace,
ID: []int64{spaceID},
Action: permission.ActionRead,
},
}
if workflowID > 0 {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeWorkflow,
ID: []int64{workflowID},
Action: permission.ActionRead,
})
}
result, err := permission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
ResourceIdentifier: rd,
OperatorID: uid,
})
if err != nil {
return fmt.Errorf("failed to check workspace permission: %w", err)
}
if result.Decision != permission.Allow {
return fmt.Errorf("user %d does not have access to space %d", uid, spaceID)
}
return nil
}
func (s *ShortcutCmdApplicationService) Handler(ctx context.Context, req *playground.CreateUpdateShortcutCommandRequest) (*playground.ShortcutCommand, error) {
var err error
uid := ctxutil.MustGetUIDFromCtx(ctx)
cr, buildErr := s.buildReq(ctx, req)
if buildErr != nil {
return nil, buildErr
}
var err error
err = checkPermission(ctx, uid, req.GetSpaceID(), cr.WorkFlowID)
if err != nil {
return nil, err
}
var cmdDO *entity.ShortcutCmd
if cr.CommandID > 0 {
cmdDO, err = s.ShortCutDomainSVC.UpdateCMD(ctx, cr)

View File

@ -26,6 +26,7 @@ import (
"github.com/coze-dev/coze-studio/backend/api/model/playground"
"github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/bizpkg/config"
"github.com/coze-dev/coze-studio/backend/bizpkg/config/modelmgr"
knowledgeModel "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
@ -47,6 +48,9 @@ import (
)
func (s *SingleAgentApplicationService) GetAgentBotInfo(ctx context.Context, req *playground.GetDraftBotInfoAgwRequest) (*playground.GetDraftBotInfoAgwResponse, error) {
uid := ctxutil.MustGetUIDFromCtx(ctx)
agentInfo, err := s.DomainSVC.GetSingleAgent(ctx, req.GetBotID(), req.GetVersion())
if err != nil {
return nil, err
@ -56,6 +60,14 @@ func (s *SingleAgentApplicationService) GetAgentBotInfo(ctx context.Context, req
return nil, errorx.New(errno.ErrAgentInvalidParamCode, errorx.KVf("msg", "agent %d not found", req.GetBotID()))
}
if agentInfo.CreatorID != uid {
return nil, errorx.New(errno.ErrAgentInvalidParamCode, errorx.KVf("msg", "agent %d not found", req.GetBotID()))
}
vo, err := s.singleAgentDraftDo2Vo(ctx, agentInfo)
if err != nil {
return nil, err

View File

@ -36,6 +36,7 @@ import (
crossdatabase "github.com/coze-dev/coze-studio/backend/crossdomain/database"
database "github.com/coze-dev/coze-studio/backend/crossdomain/database/model"
pluginConsts "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/consts"
crossuser "github.com/coze-dev/coze-studio/backend/crossdomain/user"
"github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity"
singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service"
variableEntity "github.com/coze-dev/coze-studio/backend/domain/memory/variables/entity"
@ -624,7 +625,36 @@ func (s *SingleAgentApplicationService) ListAgentPublishHistory(ctx context.Cont
return resp, nil
}
func checkUserSpace(ctx context.Context, uid int64, spaceID int64) error {
spaces, err := crossuser.DefaultSVC().GetUserSpaceList(ctx, uid)
if err != nil {
return err
}
var match bool
for _, s := range spaces {
if s.ID == spaceID {
match = true
break
}
}
if !match {
return fmt.Errorf("user %d does not have access to space %d", uid, spaceID)
}
return nil
}
func (s *SingleAgentApplicationService) ReportUserBehavior(ctx context.Context, req *playground.ReportUserBehaviorRequest) (resp *playground.ReportUserBehaviorResponse, err error) {
uid := ctxutil.MustGetUIDFromCtx(ctx)
err = checkUserSpace(ctx, uid, req.GetSpaceID())
if err != nil {
return nil, err
}
err = s.appContext.EventBus.PublishProject(ctx, &searchEntity.ProjectDomainEvent{
OpType: searchEntity.Updated,
Project: &searchEntity.ProjectDocument{

View File

@ -33,12 +33,14 @@ import (
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/bizpkg/debugutil"
crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/agentrun"
crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/conversation"
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/message"
message "github.com/coze-dev/coze-studio/backend/crossdomain/message/model"
crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/upload"
workflowModel "github.com/coze-dev/coze-studio/backend/crossdomain/workflow/model"
agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
"github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/domain/upload/service"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
@ -469,6 +471,41 @@ func (w *ApplicationService) ListApplicationConversationDef(ctx context.Context,
return resp, nil
}
func checkPermission(ctx context.Context, userID int64, workflowID int64, appID *int64, agentID *int64) error {
rd := []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeWorkflow,
ID: []int64{workflowID},
Action: permission.ActionRead,
},
}
if appID != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeApp,
ID: []int64{*appID},
Action: permission.ActionRead,
})
}
if agentID != nil {
rd = append(rd, &permission.ResourceIdentifier{
Type: permission.ResourceTypeAgent,
ID: []int64{*agentID},
Action: permission.ActionRead,
})
}
checkResult, err := permission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
OperatorID: userID,
ResourceIdentifier: rd,
})
if err != nil {
return err
}
if checkResult.Decision != permission.Allow {
return errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "no permission"))
}
return nil
}
func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workflow.ChatFlowRunRequest) (
_ *schema.StreamReader[[]*workflow.ChatFlowRunResponse], err error) {
@ -520,6 +557,11 @@ func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workfl
connectorID = mustParseInt64(req.GetConnectorID())
}
err = checkPermission(ctx, userID, workflowID, appID, agentID)
if err != nil {
return nil, err
}
if req.IsSetAppID() {
appID = ptr.Of(mustParseInt64(req.GetAppID()))
bizID = mustParseInt64(req.GetAppID())
@ -1222,7 +1264,22 @@ func (w *ApplicationService) OpenAPICreateConversation(ctx context.Context, req
//_ = spaceID
)
// todo check permission
checkResult, err := permission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
OperatorID: userID,
ResourceIdentifier: []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeWorkflow,
ID: []int64{mustParseInt64(req.GetWorkflowID())},
Action: permission.ActionRead,
},
},
})
if err != nil {
return nil, err
}
if checkResult.Decision != permission.Allow {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "no permission"))
}
if !req.GetGetOrCreate() {
cID, err = GetWorkflowDomainSVC().UpdateConversation(ctx, env, appID, req.GetConnectorId(), userID, req.GetConversationMame())

View File

@ -45,9 +45,11 @@ import (
"github.com/coze-dev/coze-studio/backend/bizpkg/debugutil"
crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge"
model "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
crosspermission "github.com/coze-dev/coze-studio/backend/crossdomain/permission"
pluginConsts "github.com/coze-dev/coze-studio/backend/crossdomain/plugin/consts"
crossuser "github.com/coze-dev/coze-studio/backend/crossdomain/user"
workflowModel "github.com/coze-dev/coze-studio/backend/crossdomain/workflow/model"
"github.com/coze-dev/coze-studio/backend/domain/permission"
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
search "github.com/coze-dev/coze-studio/backend/domain/search/entity"
domainWorkflow "github.com/coze-dev/coze-studio/backend/domain/workflow"
@ -1623,6 +1625,25 @@ func (w *ApplicationService) OpenAPIStreamResume(ctx context.Context, req *workf
apiKeyInfo := ctxutil.GetApiAuthFromCtx(ctx)
userID := apiKeyInfo.UserID
checkResult, err := crosspermission.DefaultSVC().CheckAuthz(ctx, &permission.CheckAuthzData{
OperatorID: userID,
ResourceIdentifier: []*permission.ResourceIdentifier{
{
Type: permission.ResourceTypeWorkflow,
ID: []int64{workflowID},
Action: permission.ActionRead,
},
},
})
if err != nil {
return nil, err
}
if checkResult.Decision != permission.Allow {
return nil, errorx.New(errno.ErrMemoryPermissionCode, errorx.KV("msg", "no permission"))
}
var connectorID int64
if req.IsSetConnectorID() {
connectorID = mustParseInt64(req.GetConnectorID())

View File

@ -33,6 +33,7 @@ type SingleAgent interface {
StreamExecute(ctx context.Context,
agentRuntime *AgentRuntime) (*schema.StreamReader[*model.AgentEvent], error)
ObtainAgentByIdentity(ctx context.Context, identity *model.AgentIdentity) (*model.SingleAgent, error)
GetSingleAgentDraft(ctx context.Context, agentID int64) (agentInfo *model.SingleAgent, err error)
}
type AgentRuntime struct {

View File

@ -106,3 +106,14 @@ func (c *impl) ObtainAgentByIdentity(ctx context.Context, identity *model.AgentI
}
return agentInfo.SingleAgent, nil
}
func (c *impl) GetSingleAgentDraft(ctx context.Context, agentID int64) (*model.SingleAgent, error) {
agentInfo, err := c.DomainSVC.GetSingleAgentDraft(ctx, agentID)
if err != nil {
return nil, err
}
if agentInfo == nil {
return nil, nil
}
return agentInfo.SingleAgent, nil
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 app
import (
"context"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
)
var defaultSVC AppService
func DefaultSVC() AppService {
return defaultSVC
}
func SetDefaultSVC(c AppService) {
defaultSVC = c
}
type AppService interface {
GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, err error)
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 crossapp
import (
"context"
crossapp "github.com/coze-dev/coze-studio/backend/crossdomain/app"
"github.com/coze-dev/coze-studio/backend/domain/app/entity"
"github.com/coze-dev/coze-studio/backend/domain/app/service"
)
type appServiceImpl struct {
DomainSVC service.AppService
}
func InitDomainService(domainSVC service.AppService) crossapp.AppService {
return &appServiceImpl{
DomainSVC: domainSVC,
}
}
func (a *appServiceImpl) GetDraftAPP(ctx context.Context, appID int64) (app *entity.APP, err error) {
return a.DomainSVC.GetDraftAPP(ctx, appID)
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 model
import (
"github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/common"
publishAPI "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/publish"
resourceCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
)
type APP struct {
ID int64
SpaceID int64
IconURI *string
Name *string
Desc *string
OwnerID int64
ConnectorIDs []int64
Version *string
VersionDesc *string
PublishRecordID *int64
PublishStatus *PublishStatus
PublishExtraInfo *PublishRecordExtraInfo
CreatedAtMS int64
UpdatedAtMS int64
PublishedAtMS *int64
}
func (a APP) Published() bool {
return a.PublishStatus != nil && *a.PublishStatus == PublishStatusOfPublishDone
}
func (a APP) GetPublishedAtMS() int64 {
return ptr.FromOrDefault(a.PublishedAtMS, 0)
}
func (a APP) GetVersion() string {
return ptr.FromOrDefault(a.Version, "")
}
func (a APP) GetName() string {
return ptr.FromOrDefault(a.Name, "")
}
func (a APP) GetDesc() string {
return ptr.FromOrDefault(a.Desc, "")
}
func (a APP) GetVersionDesc() string {
return ptr.FromOrDefault(a.VersionDesc, "")
}
func (a APP) GetIconURI() string {
return ptr.FromOrDefault(a.IconURI, "")
}
func (a APP) GetPublishStatus() PublishStatus {
return ptr.FromOrDefault(a.PublishStatus, 0)
}
func (a APP) GetPublishRecordID() int64 {
return ptr.FromOrDefault(a.PublishRecordID, 0)
}
type PublishRecord struct {
APP *APP
ConnectorPublishRecords []*ConnectorPublishRecord
}
type PublishRecordExtraInfo struct {
PackFailedInfo []*PackResourceFailedInfo `json:"pack_failed_info,omitempty"`
}
func (p *PublishRecordExtraInfo) ToVO() *publishAPI.PublishRecordStatusDetail {
if p == nil || len(p.PackFailedInfo) == 0 {
return &publishAPI.PublishRecordStatusDetail{}
}
packFailedDetail := make([]*publishAPI.PackFailedDetail, 0, len(p.PackFailedInfo))
for _, info := range p.PackFailedInfo {
packFailedDetail = append(packFailedDetail, &publishAPI.PackFailedDetail{
EntityID: info.ResID,
EntityType: common.ResourceType(info.ResType),
EntityName: info.ResName,
})
}
return &publishAPI.PublishRecordStatusDetail{
PackFailedDetail: packFailedDetail,
}
}
type PackResourceFailedInfo struct {
ResID int64 `json:"res_id"`
ResType resourceCommon.ResType `json:"res_type"`
ResName string `json:"res_name"`
}
type ResourceCopyResult struct {
ResID int64 `json:"res_id"`
ResType ResourceType `json:"res_type"`
ResName string `json:"res_name"`
CopyStatus ResourceCopyStatus `json:"copy_status"`
CopyScene resourceCommon.ResourceCopyScene `json:"copy_scene"`
FailedReason string `json:"reason"`
}
type Resource struct {
ResID int64
ResType ResourceType
ResName string
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 model
import (
publishAPI "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/publish"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
var ConnectorIDWhiteList = []int64{
consts.WebSDKConnectorID,
consts.APIConnectorID,
}
type ConnectorPublishRecord struct {
ConnectorID int64 `json:"connector_id"`
PublishStatus ConnectorPublishStatus `json:"publish_status"`
PublishConfig PublishConfig `json:"publish_config"`
}
type PublishConfig struct {
SelectedWorkflows []*SelectedWorkflow `json:"selected_workflows,omitempty"`
}
func (p *PublishConfig) ToVO() *publishAPI.ConnectorPublishConfig {
config := &publishAPI.ConnectorPublishConfig{
SelectedWorkflows: make([]*publishAPI.SelectedWorkflow, 0, len(p.SelectedWorkflows)),
}
if p == nil {
return config
}
for _, w := range p.SelectedWorkflows {
config.SelectedWorkflows = append(config.SelectedWorkflows, &publishAPI.SelectedWorkflow{
WorkflowID: w.WorkflowID,
WorkflowName: w.WorkflowName,
})
}
return config
}
type SelectedWorkflow struct {
WorkflowID int64 `json:"workflow_id"`
WorkflowName string `json:"workflow_name"`
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 model
type PublishStatus int
const (
PublishStatusOfPacking PublishStatus = 0
PublishStatusOfPackFailed PublishStatus = 1
PublishStatusOfAuditing PublishStatus = 2
PublishStatusOfAuditNotPass PublishStatus = 3
PublishStatusOfConnectorPublishing PublishStatus = 4
PublishStatusOfPublishDone PublishStatus = 5
)
type ConnectorPublishStatus int
const (
ConnectorPublishStatusOfDefault ConnectorPublishStatus = 0
ConnectorPublishStatusOfAuditing ConnectorPublishStatus = 1
ConnectorPublishStatusOfSuccess ConnectorPublishStatus = 2
ConnectorPublishStatusOfFailed ConnectorPublishStatus = 3
ConnectorPublishStatusOfDisable ConnectorPublishStatus = 4
)
type ResourceType string
const (
ResourceTypeOfPlugin ResourceType = "plugin"
ResourceTypeOfWorkflow ResourceType = "workflow"
ResourceTypeOfKnowledge ResourceType = "knowledge"
ResourceTypeOfDatabase ResourceType = "database"
)
type ResourceCopyStatus int
const (
ResourceCopyStatusOfSuccess ResourceCopyStatus = 1
ResourceCopyStatusOfProcessing ResourceCopyStatus = 2
ResourceCopyStatusOfFailed ResourceCopyStatus = 3
)

View File

@ -32,6 +32,8 @@ type Knowledge interface {
Store(ctx context.Context, document *model.CreateDocumentRequest) (*model.CreateDocumentResponse, error)
Delete(ctx context.Context, r *model.DeleteDocumentRequest) (*model.DeleteDocumentResponse, error)
ListKnowledgeDetail(ctx context.Context, req *model.ListKnowledgeDetailRequest) (*model.ListKnowledgeDetailResponse, error)
MGetSlice(ctx context.Context, request *model.MGetSliceRequest) (response *model.MGetSliceResponse, err error)
MGetDocument(ctx context.Context, request *model.MGetDocumentRequest) (response *model.MGetDocumentResponse, err error)
}
var defaultSVC Knowledge

View File

@ -181,6 +181,49 @@ func (i *impl) ListKnowledgeDetail(ctx context.Context, req *model.ListKnowledge
return resp, nil
}
func (i *impl) MGetSlice(ctx context.Context, request *model.MGetSliceRequest) (response *model.MGetSliceResponse, err error) {
resp, err := i.DomainSVC.MGetSlice(ctx, &service.MGetSliceRequest{
SliceIDs: request.SliceIDs,
})
if err != nil {
return nil, err
}
return &model.MGetSliceResponse{
Slices: slices.Transform(resp.Slices, func(a *entity.Slice) *model.Slice {
return &model.Slice{
Info: model.Info{
ID: a.ID,
CreatorID: a.CreatorID,
SpaceID: a.SpaceID,
},
KnowledgeID: a.KnowledgeID,
DocumentID: a.DocumentID,
}
}),
}, nil
}
func (i *impl) MGetDocument(ctx context.Context, request *model.MGetDocumentRequest) (response *model.MGetDocumentResponse, err error) {
resp, err := i.DomainSVC.MGetDocument(ctx, &service.MGetDocumentRequest{
DocumentIDs: request.DocumentIDs,
})
if err != nil {
return nil, err
}
return &model.MGetDocumentResponse{
Documents: slices.Transform(resp.Documents, func(a *entity.Document) *model.Document {
return &model.Document{
ID: a.ID,
Name: a.Name,
CreatorID: a.CreatorID,
SpaceID: a.SpaceID,
}
}),
}, nil
}
func toChunkType(typ model.ChunkType) (parser.ChunkType, error) {
switch typ {
case model.ChunkTypeDefault:

View File

@ -115,6 +115,21 @@ func (mr *MockKnowledgeMockRecorder) ListKnowledgeDetail(ctx, req any) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListKnowledgeDetail", reflect.TypeOf((*MockKnowledge)(nil).ListKnowledgeDetail), ctx, req)
}
// MGetDocument mocks base method.
func (m *MockKnowledge) MGetDocument(ctx context.Context, request *knowledge.MGetDocumentRequest) (*knowledge.MGetDocumentResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MGetDocument", ctx, request)
ret0, _ := ret[0].(*knowledge.MGetDocumentResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MGetDocument indicates an expected call of MGetDocument.
func (mr *MockKnowledgeMockRecorder) MGetDocument(ctx, request any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetDocument", reflect.TypeOf((*MockKnowledge)(nil).MGetDocument), ctx, request)
}
// MGetKnowledgeByID mocks base method.
func (m *MockKnowledge) MGetKnowledgeByID(ctx context.Context, request *knowledge.MGetKnowledgeByIDRequest) (*knowledge.MGetKnowledgeByIDResponse, error) {
m.ctrl.T.Helper()
@ -130,6 +145,21 @@ func (mr *MockKnowledgeMockRecorder) MGetKnowledgeByID(ctx, request any) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetKnowledgeByID", reflect.TypeOf((*MockKnowledge)(nil).MGetKnowledgeByID), ctx, request)
}
// MGetSlice mocks base method.
func (m *MockKnowledge) MGetSlice(ctx context.Context, request *knowledge.MGetSliceRequest) (*knowledge.MGetSliceResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MGetSlice", ctx, request)
ret0, _ := ret[0].(*knowledge.MGetSliceResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MGetSlice indicates an expected call of MGetSlice.
func (mr *MockKnowledgeMockRecorder) MGetSlice(ctx, request any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetSlice", reflect.TypeOf((*MockKnowledge)(nil).MGetSlice), ctx, request)
}
// Retrieve mocks base method.
func (m *MockKnowledge) Retrieve(ctx context.Context, req *knowledge.RetrieveRequest) (*knowledge.RetrieveResponse, error) {
m.ctrl.T.Helper()

View File

@ -347,3 +347,26 @@ type ListKnowledgeDetailRequest struct {
type ListKnowledgeDetailResponse struct {
KnowledgeDetails []*KnowledgeDetail
}
type MGetSliceRequest struct {
SliceIDs []int64
}
type MGetSliceResponse struct {
Slices []*Slice
}
type MGetDocumentRequest struct {
DocumentIDs []int64
}
type Document struct {
ID int64
Name string
CreatorID int64
SpaceID int64
}
type MGetDocumentResponse struct {
Documents []*Document
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 permission
import (
"context"
"github.com/coze-dev/coze-studio/backend/crossdomain/permission/model"
)
var defaultSVC Permission
func DefaultSVC() Permission {
return defaultSVC
}
func SetDefaultSVC(c Permission) {
defaultSVC = c
}
type Permission interface {
CheckAuthz(ctx context.Context, req *model.CheckAuthzData) (*model.CheckAuthzResult, error)
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 crosspermission
import (
"context"
crosspermission "github.com/coze-dev/coze-studio/backend/crossdomain/permission"
"github.com/coze-dev/coze-studio/backend/crossdomain/permission/model"
"github.com/coze-dev/coze-studio/backend/domain/permission"
)
type impl struct {
DomainSVC permission.Permission
}
func InitDomainService(domainSVC permission.Permission) crosspermission.Permission {
return &impl{
DomainSVC: domainSVC,
}
}
func (i *impl) CheckAuthz(ctx context.Context, req *model.CheckAuthzData) (*model.CheckAuthzResult, error) {
return i.DomainSVC.CheckAuthz(ctx, req)
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 model
import "github.com/coze-dev/coze-studio/backend/domain/permission"
type CheckAuthzData = permission.CheckAuthzData
type CheckAuthzResult = permission.CheckAuthzResult

View File

@ -0,0 +1,57 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: contract.go
//
// Generated by this command:
//
// mockgen -destination permissionmock/permission_mock.go --package permissionmock -source contract.go
//
// Package permissionmock is a generated GoMock package.
package permissionmock
import (
context "context"
reflect "reflect"
model "github.com/coze-dev/coze-studio/backend/crossdomain/permission/model"
gomock "go.uber.org/mock/gomock"
)
// MockPermission is a mock of Permission interface.
type MockPermission struct {
ctrl *gomock.Controller
recorder *MockPermissionMockRecorder
isgomock struct{}
}
// MockPermissionMockRecorder is the mock recorder for MockPermission.
type MockPermissionMockRecorder struct {
mock *MockPermission
}
// NewMockPermission creates a new mock instance.
func NewMockPermission(ctrl *gomock.Controller) *MockPermission {
mock := &MockPermission{ctrl: ctrl}
mock.recorder = &MockPermissionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPermission) EXPECT() *MockPermissionMockRecorder {
return m.recorder
}
// CheckAuthz mocks base method.
func (m *MockPermission) CheckAuthz(ctx context.Context, req *model.CheckAuthzData) (*model.CheckAuthzResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CheckAuthz", ctx, req)
ret0, _ := ret[0].(*model.CheckAuthzResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CheckAuthz indicates an expected call of CheckAuthz.
func (mr *MockPermissionMockRecorder) CheckAuthz(ctx, req any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckAuthz", reflect.TypeOf((*MockPermission)(nil).CheckAuthz), ctx, req)
}

View File

@ -24,9 +24,10 @@ import (
type EntitySpace = entity.Space
//go:generate mockgen -destination ../../../internal/mock/crossdomain/crossuser/crossuser.go --package mockCrossUser -source crossuser.go
//go:generate mockgen -destination ../../internal/mock/crossdomain/crossuser/crossuser.go --package mockCrossUser -source contract.go
type User interface {
GetUserSpaceList(ctx context.Context, userID int64) (spaces []*EntitySpace, err error)
GetUserSpaceBySpaceID(ctx context.Context, spaceID []int64) (space []*EntitySpace, err error)
}
var defaultSVC User

View File

@ -40,3 +40,7 @@ func InitDomainService(u service.User) crossuser.User {
func (u *impl) GetUserSpaceList(ctx context.Context, userID int64) (spaces []*entity.Space, err error) {
return u.DomainSVC.GetUserSpaceList(ctx, userID)
}
func (u *impl) GetUserSpaceBySpaceID(ctx context.Context, spaceID []int64) (space []*entity.Space, err error) {
return u.DomainSVC.GetUserSpaceBySpaceID(ctx, spaceID)
}

View File

@ -45,6 +45,7 @@ type Workflow interface {
WithMessagePipe() (compose.Option, *schema.StreamReader[*entity.Message], func())
StreamResume(ctx context.Context, req *entity.ResumeRequest, config workflowModel.ExecuteConfig) (*schema.StreamReader[*entity.Message], error)
InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID int64, appID int64, userID int64) error
MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workflow, int64, error)
}
type ExecuteConfig = workflowModel.ExecuteConfig

View File

@ -95,3 +95,7 @@ func (i *impl) GetWorkflowIDsByAppID(ctx context.Context, appID int64) ([]int64,
return a.ID
}), err
}
func (i *impl) MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workflow, int64, error) {
return i.DomainSVC.MGet(ctx, policy)
}

View File

@ -17,113 +17,17 @@
package entity
import (
"github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/common"
publishAPI "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/publish"
resourceCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
"github.com/coze-dev/coze-studio/backend/crossdomain/app/model"
)
type APP struct {
ID int64
SpaceID int64
IconURI *string
Name *string
Desc *string
OwnerID int64
type APP = model.APP
ConnectorIDs []int64
Version *string
VersionDesc *string
PublishRecordID *int64
PublishStatus *PublishStatus
PublishExtraInfo *PublishRecordExtraInfo
type PublishRecord = model.PublishRecord
CreatedAtMS int64
UpdatedAtMS int64
PublishedAtMS *int64
}
type PublishRecordExtraInfo = model.PublishRecordExtraInfo
func (a APP) Published() bool {
return a.PublishStatus != nil && *a.PublishStatus == PublishStatusOfPublishDone
}
type PackResourceFailedInfo = model.PackResourceFailedInfo
func (a APP) GetPublishedAtMS() int64 {
return ptr.FromOrDefault(a.PublishedAtMS, 0)
}
type ResourceCopyResult = model.ResourceCopyResult
func (a APP) GetVersion() string {
return ptr.FromOrDefault(a.Version, "")
}
func (a APP) GetName() string {
return ptr.FromOrDefault(a.Name, "")
}
func (a APP) GetDesc() string {
return ptr.FromOrDefault(a.Desc, "")
}
func (a APP) GetVersionDesc() string {
return ptr.FromOrDefault(a.VersionDesc, "")
}
func (a APP) GetIconURI() string {
return ptr.FromOrDefault(a.IconURI, "")
}
func (a APP) GetPublishStatus() PublishStatus {
return ptr.FromOrDefault(a.PublishStatus, 0)
}
func (a APP) GetPublishRecordID() int64 {
return ptr.FromOrDefault(a.PublishRecordID, 0)
}
type PublishRecord struct {
APP *APP
ConnectorPublishRecords []*ConnectorPublishRecord
}
type PublishRecordExtraInfo struct {
PackFailedInfo []*PackResourceFailedInfo `json:"pack_failed_info,omitempty"`
}
func (p *PublishRecordExtraInfo) ToVO() *publishAPI.PublishRecordStatusDetail {
if p == nil || len(p.PackFailedInfo) == 0 {
return &publishAPI.PublishRecordStatusDetail{}
}
packFailedDetail := make([]*publishAPI.PackFailedDetail, 0, len(p.PackFailedInfo))
for _, info := range p.PackFailedInfo {
packFailedDetail = append(packFailedDetail, &publishAPI.PackFailedDetail{
EntityID: info.ResID,
EntityType: common.ResourceType(info.ResType),
EntityName: info.ResName,
})
}
return &publishAPI.PublishRecordStatusDetail{
PackFailedDetail: packFailedDetail,
}
}
type PackResourceFailedInfo struct {
ResID int64 `json:"res_id"`
ResType resourceCommon.ResType `json:"res_type"`
ResName string `json:"res_name"`
}
type ResourceCopyResult struct {
ResID int64 `json:"res_id"`
ResType ResourceType `json:"res_type"`
ResName string `json:"res_name"`
CopyStatus ResourceCopyStatus `json:"copy_status"`
CopyScene resourceCommon.ResourceCopyScene `json:"copy_scene"`
FailedReason string `json:"reason"`
}
type Resource struct {
ResID int64
ResType ResourceType
ResName string
}
type Resource = model.Resource

View File

@ -17,7 +17,7 @@
package entity
import (
publishAPI "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/publish"
"github.com/coze-dev/coze-studio/backend/crossdomain/app/model"
"github.com/coze-dev/coze-studio/backend/types/consts"
)
@ -26,36 +26,6 @@ var ConnectorIDWhiteList = []int64{
consts.APIConnectorID,
}
type ConnectorPublishRecord struct {
ConnectorID int64 `json:"connector_id"`
PublishStatus ConnectorPublishStatus `json:"publish_status"`
PublishConfig PublishConfig `json:"publish_config"`
}
type PublishConfig struct {
SelectedWorkflows []*SelectedWorkflow `json:"selected_workflows,omitempty"`
}
func (p *PublishConfig) ToVO() *publishAPI.ConnectorPublishConfig {
config := &publishAPI.ConnectorPublishConfig{
SelectedWorkflows: make([]*publishAPI.SelectedWorkflow, 0, len(p.SelectedWorkflows)),
}
if p == nil {
return config
}
for _, w := range p.SelectedWorkflows {
config.SelectedWorkflows = append(config.SelectedWorkflows, &publishAPI.SelectedWorkflow{
WorkflowID: w.WorkflowID,
WorkflowName: w.WorkflowName,
})
}
return config
}
type SelectedWorkflow struct {
WorkflowID int64 `json:"workflow_id"`
WorkflowName string `json:"workflow_name"`
}
type ConnectorPublishRecord = model.ConnectorPublishRecord
type PublishConfig = model.PublishConfig
type SelectedWorkflow = model.SelectedWorkflow

View File

@ -16,7 +16,9 @@
package entity
type PublishStatus int
import "github.com/coze-dev/coze-studio/backend/crossdomain/app/model"
type PublishStatus = model.PublishStatus
const (
PublishStatusOfPacking PublishStatus = 0
@ -27,7 +29,7 @@ const (
PublishStatusOfPublishDone PublishStatus = 5
)
type ConnectorPublishStatus int
type ConnectorPublishStatus = model.ConnectorPublishStatus
const (
ConnectorPublishStatusOfDefault ConnectorPublishStatus = 0
@ -37,7 +39,7 @@ const (
ConnectorPublishStatusOfDisable ConnectorPublishStatus = 4
)
type ResourceType string
type ResourceType = model.ResourceType
const (
ResourceTypeOfPlugin ResourceType = "plugin"
@ -46,7 +48,7 @@ const (
ResourceTypeOfDatabase ResourceType = "database"
)
type ResourceCopyStatus int
type ResourceCopyStatus = model.ResourceCopyStatus
const (
ResourceCopyStatusOfSuccess ResourceCopyStatus = 1

View File

@ -57,6 +57,8 @@ type Knowledge interface {
ListSlice(ctx context.Context, request *ListSliceRequest) (response *ListSliceResponse, err error)
ListPhotoSlice(ctx context.Context, request *ListPhotoSliceRequest) (response *ListPhotoSliceResponse, err error)
GetSlice(ctx context.Context, request *GetSliceRequest) (response *GetSliceResponse, err error)
MGetSlice(ctx context.Context, request *MGetSliceRequest) (response *MGetSliceResponse, err error)
MGetDocument(ctx context.Context, request *MGetDocumentRequest) (response *MGetDocumentResponse, err error)
Retrieve(ctx context.Context, request *RetrieveRequest) (response *RetrieveResponse, err error)
CreateDocumentReview(ctx context.Context, request *CreateDocumentReviewRequest) (response *CreateDocumentReviewResponse, err error)
MGetDocumentReview(ctx context.Context, request *MGetDocumentReviewRequest) (response *MGetDocumentReviewResponse, err error)
@ -354,5 +356,21 @@ type ExtractPhotoCaptionRequest struct {
type ExtractPhotoCaptionResponse struct {
Caption string
}
type MGetSliceRequest struct {
SliceIDs []int64
}
type MGetSliceResponse struct {
Slices []*entity.Slice
}
type MGetDocumentRequest struct {
DocumentIDs []int64
}
type MGetDocumentResponse struct {
Documents []*entity.Document
}
type MGetKnowledgeByIDRequest = knowledge.MGetKnowledgeByIDRequest
type MGetKnowledgeByIDResponse = knowledge.MGetKnowledgeByIDResponse

View File

@ -1503,3 +1503,45 @@ func (k *knowledgeSVC) genMultiIDs(ctx context.Context, counts int) ([]int64, er
}
return allIDs, nil
}
func (k *knowledgeSVC) MGetSlice(ctx context.Context, request *MGetSliceRequest) (response *MGetSliceResponse, err error) {
slices, err := k.sliceRepo.MGetSlices(ctx, request.SliceIDs)
if err != nil {
return nil, err
}
var result []*entity.Slice
for _, slice := range slices {
if slice != nil {
result = append(result, k.fromModelSlice(ctx, slice))
}
}
return &MGetSliceResponse{
Slices: result,
}, nil
}
func (k *knowledgeSVC) MGetDocument(ctx context.Context, request *MGetDocumentRequest) (response *MGetDocumentResponse, err error) {
documents, err := k.documentRepo.MGetByID(ctx, request.DocumentIDs)
if err != nil {
return nil, err
}
var result []*entity.Document
for _, doc := range documents {
if doc != nil {
docEntity, err := k.fromModelDocument(ctx, doc)
if err != nil {
return nil, err
}
if docEntity != nil {
result = append(result, docEntity)
}
}
}
return &MGetDocumentResponse{
Documents: result,
}, nil
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 permission
import (
"context"
"fmt"
)
type ResourcePermissionRequest struct {
ResourceType ResourceType
ResourceIDs []int64
Action Action
OperatorID int64
IsDraft *bool
}
type ResourceInfo struct {
ID int64
CreatorID int64
SpaceID *int64
}
type ResourceQueryer interface {
QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error)
GetResourceType() ResourceType
}
type AuthzChecker struct {
resourceQueryers map[ResourceType]ResourceQueryer
}
func NewAuthzChecker() *AuthzChecker {
checker := &AuthzChecker{
resourceQueryers: make(map[ResourceType]ResourceQueryer),
}
checker.registerResourceQueryers()
return checker
}
func (c *AuthzChecker) registerResourceQueryers() {
c.resourceQueryers[ResourceTypeWorkspace] = NewWorkspaceResourceQueryer()
c.resourceQueryers[ResourceTypeAgent] = NewAgentResourceQueryer()
c.resourceQueryers[ResourceTypePlugin] = NewPluginResourceQueryer()
c.resourceQueryers[ResourceTypeWorkflow] = NewWorkflowResourceQueryer()
c.resourceQueryers[ResourceTypeKnowledge] = NewKnowledgeResourceQueryer()
c.resourceQueryers[ResourceTypeKnowledgeSlice] = NewKnowledgeSliceResourceQueryer()
c.resourceQueryers[ResourceTypeKnowledgeDocument] = NewKnowledgeDocumentResourceQueryer()
c.resourceQueryers[ResourceTypeDatabase] = NewDatabaseResourceQueryer()
c.resourceQueryers[ResourceTypeApp] = NewAppResourceQueryer()
}
func (c *AuthzChecker) CheckResourcePermission(ctx context.Context, req *ResourcePermissionRequest) (bool, error) {
queryeer, exists := c.resourceQueryers[req.ResourceType]
if !exists {
return false, fmt.Errorf("unsupported resource type: %d", req.ResourceType)
}
resourceInfos, err := queryeer.QueryResourceInfo(ctx, req.ResourceIDs, req.IsDraft)
if err != nil {
return false, fmt.Errorf("failed to query resource info: %w", err)
}
for _, resourceInfo := range resourceInfos {
allowed := c.checkSingleResourcePermission(req.OperatorID, resourceInfo, req.Action)
if !allowed {
return false, nil
}
}
return true, nil
}
func (c *AuthzChecker) checkSingleResourcePermission(operatorID int64, resourceInfo *ResourceInfo, action Action) bool {
if operatorID == resourceInfo.CreatorID {
return true
}
switch action {
case ActionRead:
return c.checkReadPermission(operatorID, resourceInfo)
case ActionWrite:
return c.checkWritePermission(operatorID, resourceInfo)
default:
return false
}
}
func (c *AuthzChecker) checkReadPermission(operatorID int64, resourceInfo *ResourceInfo) bool {
return operatorID == resourceInfo.CreatorID
}
func (c *AuthzChecker) checkWritePermission(operatorID int64, resourceInfo *ResourceInfo) bool {
return operatorID == resourceInfo.CreatorID
}

View File

@ -16,11 +16,17 @@
package permission
type (
ResourceType int
Decision int
Action string
)
const (
ResourceTypeAccount ResourceType = 1
ResourceTypeWorkspace = 2
ResourceTypeApp = 3
ResourceTypeBot = 4
ResourceTypeAgent = 4
ResourceTypePlugin = 5
ResourceTypeWorkflow = 6
ResourceTypeKnowledge = 7
@ -42,6 +48,8 @@ const (
ResourceTypeDatabase = 23
ResourceTypeOceanProject = 24
ResourceTypeFinetuneTask = 25
ResourceTypeKnowledgeDocument = 26
ResourceTypeKnowledgeSlice = 27
)
const (
@ -50,3 +58,8 @@ const (
// Deny represents permission denied
Deny Decision = 2
)
const (
ActionRead Action = "read"
ActionWrite Action = "write"
)

View File

@ -20,33 +20,26 @@ import (
"context"
)
type (
ResourceType int
Decision int
)
type ResourceIdentifier struct {
Type ResourceType
ID string
Type ResourceType
ID []int64
Action Action
}
type ActionAndResource struct {
Action string
Action Action
ResourceIdentifier ResourceIdentifier
}
type CheckPermissionRequest struct {
IdentityTicket string
ActionAndResources []ActionAndResource
type CheckAuthzData struct {
ResourceIdentifier []*ResourceIdentifier
OperatorID int64
IsDraft *bool
}
type CheckPermissionResponse struct {
type CheckAuthzResult struct {
Decision Decision
}
type Permission interface {
CheckPermission(ctx context.Context, req *CheckPermissionRequest) (*CheckPermissionResponse, error)
CheckSingleAgentOperatePermission(ctx context.Context, botID, spaceID int64) (bool, error)
CheckSpaceOperatePermission(ctx context.Context, spaceID int64, path, ticket string) (bool, error)
UserSpaceCheck(ctx context.Context, spaceId, userId int64) (bool, error)
CheckAuthz(ctx context.Context, req *CheckAuthzData) (*CheckAuthzResult, error)
}

View File

@ -26,18 +26,30 @@ func NewService() Permission {
return &permissionImpl{}
}
func (p *permissionImpl) CheckPermission(ctx context.Context, req *CheckPermissionRequest) (*CheckPermissionResponse, error) {
return &CheckPermissionResponse{Decision: 0}, nil
func DefaultSVC() Permission {
return NewService()
}
func (p *permissionImpl) CheckSingleAgentOperatePermission(ctx context.Context, botID, spaceID int64) (bool, error) {
return true, nil
}
func (p *permissionImpl) CheckAuthz(ctx context.Context, req *CheckAuthzData) (*CheckAuthzResult, error) {
func (p *permissionImpl) CheckSpaceOperatePermission(ctx context.Context, spaceID int64, path, ticket string) (bool, error) {
return true, nil
}
authzChecker := NewAuthzChecker()
func (p *permissionImpl) UserSpaceCheck(ctx context.Context, spaceId, userId int64) (bool, error) {
return true, nil
for _, resourceIdentifier := range req.ResourceIdentifier {
allowed, err := authzChecker.CheckResourcePermission(ctx, &ResourcePermissionRequest{
ResourceType: resourceIdentifier.Type,
ResourceIDs: resourceIdentifier.ID,
Action: resourceIdentifier.Action,
OperatorID: req.OperatorID,
IsDraft: req.IsDraft,
})
if err != nil {
return nil, err
}
if !allowed {
return &CheckAuthzResult{Decision: Deny}, nil
}
}
return &CheckAuthzResult{Decision: Allow}, nil
}

View File

@ -0,0 +1,369 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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 permission
import (
"context"
"fmt"
"github.com/coze-dev/coze-studio/backend/api/model/data/database/table"
"github.com/coze-dev/coze-studio/backend/crossdomain/agent"
"github.com/coze-dev/coze-studio/backend/crossdomain/app"
"github.com/coze-dev/coze-studio/backend/crossdomain/database"
"github.com/coze-dev/coze-studio/backend/crossdomain/knowledge"
"github.com/coze-dev/coze-studio/backend/crossdomain/plugin"
"github.com/coze-dev/coze-studio/backend/crossdomain/user"
"github.com/coze-dev/coze-studio/backend/crossdomain/workflow"
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
databaseModel "github.com/coze-dev/coze-studio/backend/crossdomain/database/model"
knowledgeModel "github.com/coze-dev/coze-studio/backend/crossdomain/knowledge/model"
)
type AgentResourceQueryer struct {
agentService agent.SingleAgent
}
func NewAgentResourceQueryer() *AgentResourceQueryer {
return &AgentResourceQueryer{
agentService: agent.DefaultSVC(),
}
}
func (q *AgentResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
var result []*ResourceInfo
for _, id := range resourceIDs {
agentInfo, err := q.agentService.GetSingleAgentDraft(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to query bot %d: %w", id, err)
}
if agentInfo != nil {
result = append(result, &ResourceInfo{
ID: id,
CreatorID: agentInfo.CreatorID,
SpaceID: &agentInfo.SpaceID,
})
}
}
return result, nil
}
func (q *AgentResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeAgent
}
type PluginResourceQueryer struct {
pluginService plugin.PluginService
}
func NewPluginResourceQueryer() *PluginResourceQueryer {
return &PluginResourceQueryer{
pluginService: plugin.DefaultSVC(),
}
}
func (q *PluginResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
plugins, err := q.pluginService.MGetDraftPlugins(ctx, resourceIDs)
if err != nil {
return nil, fmt.Errorf("failed to query draft plugins: %w", err)
}
var result []*ResourceInfo
for _, plugin := range plugins {
result = append(result, &ResourceInfo{
ID: plugin.ID,
CreatorID: plugin.DeveloperID,
SpaceID: &plugin.SpaceID,
})
}
return result, nil
}
func (q *PluginResourceQueryer) GetResourceType() ResourceType {
return ResourceTypePlugin
}
type WorkflowResourceQueryer struct {
workflowService crossworkflow.Workflow
}
func NewWorkflowResourceQueryer() *WorkflowResourceQueryer {
return &WorkflowResourceQueryer{
workflowService: crossworkflow.DefaultSVC(),
}
}
func (q *WorkflowResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
workflows, _, err := q.workflowService.MGet(ctx, &vo.MGetPolicy{
QType: crossworkflow.FromDraft,
MetaOnly: true,
MetaQuery: vo.MetaQuery{
IDs: resourceIDs,
},
})
if err != nil {
return nil, fmt.Errorf("failed to query workflows: %w", err)
}
var result []*ResourceInfo
for _, workflow := range workflows {
result = append(result, &ResourceInfo{
ID: workflow.ID,
CreatorID: workflow.CreatorID,
SpaceID: &workflow.SpaceID,
})
}
return result, nil
}
func (q *WorkflowResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeWorkflow
}
type KnowledgeResourceQueryer struct {
knowledgeService knowledge.Knowledge
}
func NewKnowledgeResourceQueryer() *KnowledgeResourceQueryer {
return &KnowledgeResourceQueryer{
knowledgeService: knowledge.DefaultSVC(),
}
}
func (q *KnowledgeResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
resp, err := q.knowledgeService.MGetKnowledgeByID(ctx, &knowledgeModel.MGetKnowledgeByIDRequest{
KnowledgeIDs: resourceIDs,
})
if err != nil {
return nil, fmt.Errorf("failed to query knowledge: %w", err)
}
var result []*ResourceInfo
for _, knowledgeInfo := range resp.Knowledge {
if knowledgeInfo != nil {
result = append(result, &ResourceInfo{
ID: knowledgeInfo.ID,
CreatorID: knowledgeInfo.CreatorID,
SpaceID: &knowledgeInfo.SpaceID,
})
}
}
return result, nil
}
func (q *KnowledgeResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeKnowledge
}
type DatabaseResourceQueryer struct {
databaseService database.Database
}
func NewDatabaseResourceQueryer() *DatabaseResourceQueryer {
return &DatabaseResourceQueryer{
databaseService: database.DefaultSVC(),
}
}
func (q *DatabaseResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
var basics []*databaseModel.DatabaseBasic
for _, id := range resourceIDs {
basic := &databaseModel.DatabaseBasic{
ID: id,
TableType: table.TableType_DraftTable,
}
if isDraft != nil && !*isDraft {
basic.TableType = table.TableType_OnlineTable
}
basics = append(basics, basic)
}
resp, err := q.databaseService.MGetDatabase(ctx, &databaseModel.MGetDatabaseRequest{
Basics: basics,
})
if err != nil {
return nil, fmt.Errorf("failed to query database: %w", err)
}
var result []*ResourceInfo
for _, dbInfo := range resp.Databases {
if dbInfo != nil {
result = append(result, &ResourceInfo{
ID: dbInfo.ID,
CreatorID: dbInfo.CreatorID,
SpaceID: &dbInfo.SpaceID,
})
}
}
return result, nil
}
func (q *DatabaseResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeDatabase
}
type AppResourceQueryer struct {
appService app.AppService
}
func NewAppResourceQueryer() *AppResourceQueryer {
return &AppResourceQueryer{
appService: app.DefaultSVC(),
}
}
func (q *AppResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
var result []*ResourceInfo
for _, id := range resourceIDs {
appInfo, err := q.appService.GetDraftAPP(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to query app %d: %w", id, err)
}
if appInfo != nil {
result = append(result, &ResourceInfo{
ID: id,
CreatorID: appInfo.OwnerID,
SpaceID: &appInfo.SpaceID,
})
}
}
return result, nil
}
func (q *AppResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeApp
}
type KnowledgeSliceResourceQueryer struct {
knowledgeService knowledge.Knowledge
}
func NewKnowledgeSliceResourceQueryer() *KnowledgeSliceResourceQueryer {
return &KnowledgeSliceResourceQueryer{
knowledgeService: knowledge.DefaultSVC(),
}
}
func (q *KnowledgeSliceResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
resp, err := q.knowledgeService.MGetSlice(ctx, &knowledgeModel.MGetSliceRequest{
SliceIDs: resourceIDs,
})
if err != nil {
return nil, fmt.Errorf("failed to query knowledge slice: %w", err)
}
var result []*ResourceInfo
for _, slice := range resp.Slices {
if slice != nil {
result = append(result, &ResourceInfo{
ID: slice.ID,
CreatorID: slice.CreatorID,
SpaceID: &slice.SpaceID,
})
}
}
return result, nil
}
func (q *KnowledgeSliceResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeKnowledgeSlice
}
type KnowledgeDocumentResourceQueryer struct {
knowledgeService knowledge.Knowledge
}
func NewKnowledgeDocumentResourceQueryer() *KnowledgeDocumentResourceQueryer {
return &KnowledgeDocumentResourceQueryer{
knowledgeService: knowledge.DefaultSVC(),
}
}
func (q *KnowledgeDocumentResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
resp, err := q.knowledgeService.MGetDocument(ctx, &knowledgeModel.MGetDocumentRequest{
DocumentIDs: resourceIDs,
})
if err != nil {
return nil, fmt.Errorf("failed to query knowledge document: %w", err)
}
var result []*ResourceInfo
for _, document := range resp.Documents {
if document != nil {
result = append(result, &ResourceInfo{
ID: document.ID,
CreatorID: document.CreatorID,
SpaceID: &document.SpaceID,
})
}
}
return result, nil
}
func (q *KnowledgeDocumentResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeKnowledgeDocument
}
type WorkspaceResourceQueryer struct {
userService crossuser.User
}
func NewWorkspaceResourceQueryer() *WorkspaceResourceQueryer {
return &WorkspaceResourceQueryer{
userService: crossuser.DefaultSVC(),
}
}
func (q *WorkspaceResourceQueryer) QueryResourceInfo(ctx context.Context, resourceIDs []int64, isDraft *bool) ([]*ResourceInfo, error) {
// For workspace resources, we need to get space information for each user
var result []*ResourceInfo
spaces, err := q.userService.GetUserSpaceBySpaceID(ctx, resourceIDs)
if err != nil {
return nil, fmt.Errorf("failed to get user space list for space %v: %w", resourceIDs, err)
}
for _, space := range spaces {
if space != nil {
result = append(result, &ResourceInfo{
ID: space.ID,
CreatorID: space.CreatorID,
SpaceID: &space.ID,
})
}
}
return result, nil
}
func (q *WorkspaceResourceQueryer) GetResourceType() ResourceType {
return ResourceTypeWorkspace
}

View File

@ -78,6 +78,7 @@ type User interface {
MGetUserProfiles(ctx context.Context, userIDs []int64) (users []*entity.User, err error)
ValidateSession(ctx context.Context, sessionKey string) (session *entity.Session, exist bool, err error)
GetUserSpaceList(ctx context.Context, userID int64) (spaces []*entity.Space, err error)
GetUserSpaceBySpaceID(ctx context.Context, spaceID []int64) (space []*entity.Space, err error)
}
type SaasUserProvider interface {

View File

@ -465,6 +465,40 @@ func (u *userImpl) GetUserSpaceList(ctx context.Context, userID int64) (spaces [
}), nil
}
func (u *userImpl) GetUserSpaceBySpaceID(ctx context.Context, spaceID []int64) (spaces []*userEntity.Space, err error) {
if len(spaceID) == 0 {
return nil, errorx.New(errno.ErrUserInvalidParamCode, errorx.KV("msg", "spaceID cannot be empty"))
}
spaceModels, err := u.SpaceRepo.GetSpaceByIDs(ctx, spaceID)
if err != nil {
return nil, err
}
if len(spaceModels) == 0 {
return []*userEntity.Space{}, nil
}
uris := slices.ToMap(spaceModels, func(sm *model.Space) (string, bool) {
return sm.IconURI, false
})
urls := make(map[string]string, len(uris))
for uri := range uris {
if uri != "" {
url, err := u.IconOSS.GetObjectUrl(ctx, uri)
if err != nil {
return nil, err
}
urls[uri] = url
}
}
return slices.Transform(spaceModels, func(sm *model.Space) *userEntity.Space {
return spacePo2Do(sm, urls[sm.IconURI])
}), nil
}
func spacePo2Do(space *model.Space, iconUrl string) *userEntity.Space {
return &userEntity.Space{
ID: space.ID,

View File

@ -1,9 +1,25 @@
/*
* Copyright 2025 coze-dev Authors
*
* 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.
*/
// Code generated by MockGen. DO NOT EDIT.
// Source: crossuser.go
// Source: contract.go
//
// Generated by this command:
//
// mockgen -destination ../../../internal/mock/crossdomain/crossuser/crossuser.go --package mockCrossUser -source crossuser.go
// mockgen -destination ../../internal/mock/crossdomain/crossuser/crossuser.go --package mockCrossUser -source contract.go
//
// Package mockCrossUser is a generated GoMock package.
@ -13,9 +29,8 @@ import (
context "context"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
crossuser "github.com/coze-dev/coze-studio/backend/crossdomain/user"
gomock "go.uber.org/mock/gomock"
)
// MockUser is a mock of User interface.
@ -42,6 +57,21 @@ func (m *MockUser) EXPECT() *MockUserMockRecorder {
return m.recorder
}
// GetUserSpaceBySpaceID mocks base method.
func (m *MockUser) GetUserSpaceBySpaceID(ctx context.Context, spaceID []int64) ([]*crossuser.EntitySpace, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserSpaceBySpaceID", ctx, spaceID)
ret0, _ := ret[0].([]*crossuser.EntitySpace)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserSpaceBySpaceID indicates an expected call of GetUserSpaceBySpaceID.
func (mr *MockUserMockRecorder) GetUserSpaceBySpaceID(ctx, spaceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserSpaceBySpaceID", reflect.TypeOf((*MockUser)(nil).GetUserSpaceBySpaceID), ctx, spaceID)
}
// GetUserSpaceList mocks base method.
func (m *MockUser) GetUserSpaceList(ctx context.Context, userID int64) ([]*crossuser.EntitySpace, error) {
m.ctrl.T.Helper()