feat: add tenant bound restriction for IM dashboard and management pages
1. extract isTenantBound utility from user store logic 2. add disabled state to tenant select components across all IM pages 3. update policy tenant options logic to hide global option for bound tenants 4. add backend scope validation for IM admin operations
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type imAdminScope struct {
|
||||
@@ -85,6 +86,43 @@ func resolveImAdminBoundTenant(ctx context.Context) (int64, error) {
|
||||
return tenantId, nil
|
||||
}
|
||||
|
||||
// ensureImRecordTenantAllowed 校验目标记录/租户是否在当前操作者允许范围内。
|
||||
// 平台管理员(超管/平台角色/未绑定租户的公司部门)可访问任意租户的记录;
|
||||
// 其余账号强制限定为自己绑定的租户(与 resolveImAdminScope 的范围逻辑一致)。
|
||||
// 适用于入参不含租户ID、需按已查出的记录租户做后置校验的场景。
|
||||
func ensureImRecordTenantAllowed(ctx context.Context, recordTenantId int64) error {
|
||||
if isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) {
|
||||
return nil
|
||||
}
|
||||
if !contexts.IsTenantDept(ctx) && !(contexts.IsCompanyDept(ctx) && contexts.GetTenantId(ctx) > 0) {
|
||||
return gerror.New("当前账号无权管理IM租户")
|
||||
}
|
||||
boundTenantId, err := resolveImAdminBoundTenant(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if boundTenantId != recordTenantId {
|
||||
return gerror.New("无权访问其他商户的IM数据")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookupImTenantByCode 按租户编码查询租户(用于入参只有租户编码的管理端接口)
|
||||
func lookupImTenantByCode(ctx context.Context, code string) (tenant *entity.ImTenant, err error) {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return nil, gerror.New("请选择IM租户")
|
||||
}
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
|
||||
Where(dao.ImTenant.Columns().Code, code).Scan(&tenant); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil || tenant.Id <= 0 {
|
||||
return nil, gerror.New("IM租户不存在")
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
func ensureImTenantActive(ctx context.Context, tenantId int64) (err error) {
|
||||
var tenant *entity.ImTenant
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(tenantId).Scan(&tenant); err != nil {
|
||||
|
||||
@@ -10,18 +10,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type sSysImDeadLetter struct{}
|
||||
@@ -39,7 +34,7 @@ func (s *sSysImDeadLetter) List(ctx context.Context, in *sysin.ImDeadLetterListI
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,7 +76,7 @@ func (s *sSysImDeadLetter) View(ctx context.Context, in *sysin.ImDeadLetterViewI
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -120,7 +115,7 @@ func (s *sSysImDeadLetter) publishActionCommand(ctx context.Context, in *sysin.I
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,22 +157,6 @@ func (s *sSysImDeadLetter) publishActionCommand(ctx context.Context, in *sysin.I
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImDeadLetter) getTenant(ctx context.Context, tenantId int64) (tenant *entity.ImTenant, err error) {
|
||||
if tenantId <= 0 {
|
||||
return nil, gerror.New("请选择IM租户")
|
||||
}
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(tenantId).Scan(&tenant); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil || tenant.Id <= 0 {
|
||||
return nil, gerror.New("IM租户不存在")
|
||||
}
|
||||
if tenant.State != consts.ImTenantStateActive {
|
||||
return nil, gerror.New("IM租户未启用")
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
func convertDeadLetterModel(in *sysin.ImDeadLetterListModelRaw) *sysin.ImDeadLetterListModel {
|
||||
if in == nil {
|
||||
return &sysin.ImDeadLetterListModel{}
|
||||
|
||||
@@ -33,7 +33,12 @@ func (s *sSysImKafkaCommand) List(ctx context.Context, in *sysin.ImKafkaCommandL
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
where, args := buildImKafkaCommandWhere(in)
|
||||
// 强制租户范围:商户绑定账号忽略前端所选租户,平台管理员使用所选租户
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
where, args := buildImKafkaCommandWhere(in, scope.TenantId)
|
||||
db := imtenant.RegistryDB()
|
||||
totalValue, err := db.GetValue(ctx, "SELECT COUNT(*) FROM im_admin_commands "+where, args...)
|
||||
if err != nil {
|
||||
@@ -91,16 +96,16 @@ func (s *sSysImKafkaCommand) View(ctx context.Context, in *sysin.ImKafkaCommandV
|
||||
if err = row.Struct(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "解析IM后台命令详情失败,请稍后重试!")
|
||||
}
|
||||
// 后置校验记录归属租户是否在操作者允许范围内
|
||||
if err = ensureImRecordTenantAllowed(ctx, res.TenantId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildImKafkaCommandWhere(in *sysin.ImKafkaCommandListInp) (string, []any) {
|
||||
parts := []string{"1=1"}
|
||||
args := []any{}
|
||||
if in.TenantId > 0 {
|
||||
parts = append(parts, "tenant_id=?")
|
||||
args = append(args, in.TenantId)
|
||||
}
|
||||
func buildImKafkaCommandWhere(in *sysin.ImKafkaCommandListInp, scopeTenantId int64) (string, []any) {
|
||||
parts := []string{"tenant_id=?"}
|
||||
args := []any{scopeTenantId}
|
||||
if in.CommandId != "" {
|
||||
parts = append(parts, "command_id LIKE ?")
|
||||
args = append(args, "%"+in.CommandId+"%")
|
||||
@@ -136,6 +141,14 @@ func (s *sSysImKafkaCommand) CacheInvalidate(ctx context.Context, in *sysin.ImKa
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 校验目标租户是否在操作者允许范围内
|
||||
tenant, err := lookupImTenantByCode(ctx, in.TenantCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ensureImRecordTenantAllowed(ctx, tenant.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actor := &imkafka.Actor{
|
||||
Type: "admin",
|
||||
@@ -170,6 +183,14 @@ func (s *sSysImKafkaCommand) WorkbenchEvent(ctx context.Context, in *sysin.ImKaf
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 校验目标租户是否在操作者允许范围内
|
||||
tenant, err := lookupImTenantByCode(ctx, in.TenantCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ensureImRecordTenantAllowed(ctx, tenant.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actor := &imkafka.Actor{
|
||||
Type: "admin",
|
||||
|
||||
Reference in New Issue
Block a user