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
174 lines
5.3 KiB
Go
174 lines
5.3 KiB
Go
package sys
|
|
|
|
import (
|
|
"context"
|
|
"hotgo/internal/consts"
|
|
"hotgo/internal/dao"
|
|
"hotgo/internal/library/contexts"
|
|
"hotgo/internal/library/imtenant"
|
|
"hotgo/internal/model/entity"
|
|
"hotgo/internal/service"
|
|
"hotgo/utility/simple"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"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 {
|
|
TenantId int64
|
|
OperatorId int64
|
|
RoleKey string
|
|
RequestId string
|
|
}
|
|
|
|
const (
|
|
imAdminOperationAuditTable = "im_admin_operation_audit"
|
|
)
|
|
|
|
func resolveImAdminScope(ctx context.Context, selectedTenantId int64) (scope imAdminScope, err error) {
|
|
return resolveImAdminScopeWithAll(ctx, selectedTenantId, false)
|
|
}
|
|
|
|
func resolveImAdminScopeWithAll(ctx context.Context, selectedTenantId int64, allowAll bool) (scope imAdminScope, err error) {
|
|
operatorId := contexts.GetUserId(ctx)
|
|
scope.OperatorId = operatorId
|
|
scope.RoleKey = contexts.GetRoleKey(ctx)
|
|
scope.RequestId = gctx.CtxId(ctx)
|
|
if isImPlatformAdmin(ctx, operatorId) {
|
|
if selectedTenantId <= 0 {
|
|
if allowAll {
|
|
return scope, nil
|
|
}
|
|
return scope, gerror.New("请选择IM租户")
|
|
}
|
|
if err = ensureImTenantActive(ctx, selectedTenantId); err != nil {
|
|
return scope, err
|
|
}
|
|
scope.TenantId = selectedTenantId
|
|
return scope, nil
|
|
}
|
|
|
|
if !contexts.IsTenantDept(ctx) && !(contexts.IsCompanyDept(ctx) && contexts.GetTenantId(ctx) > 0) {
|
|
return scope, gerror.New("当前账号无权管理IM租户")
|
|
}
|
|
tenantId, err := resolveImAdminBoundTenant(ctx)
|
|
if err != nil {
|
|
return scope, err
|
|
}
|
|
if err = ensureImTenantActive(ctx, tenantId); err != nil {
|
|
return scope, err
|
|
}
|
|
scope.TenantId = tenantId
|
|
return scope, nil
|
|
}
|
|
|
|
func isImPlatformAdmin(ctx context.Context, operatorId int64) bool {
|
|
if service.AdminMember().VerifySuperId(ctx, operatorId) {
|
|
return true
|
|
}
|
|
if contexts.GetRoleKey(ctx) == consts.ImPlatformRoleKey {
|
|
return true
|
|
}
|
|
return contexts.IsCompanyDept(ctx) && contexts.GetTenantId(ctx) <= 0
|
|
}
|
|
|
|
func resolveImAdminBoundTenant(ctx context.Context) (int64, error) {
|
|
tenantId := contexts.GetTenantId(ctx)
|
|
if tenantId <= 0 {
|
|
return 0, gerror.New("当前管理员未关联IM租户")
|
|
}
|
|
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 {
|
|
return gerror.Wrap(err, "获取IM租户失败")
|
|
}
|
|
if tenant == nil || tenant.Id <= 0 {
|
|
return gerror.New("IM租户不存在")
|
|
}
|
|
if tenant.State != consts.ImTenantStateActive {
|
|
return gerror.New("IM租户未启用")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveImTenantDB(ctx context.Context, selectedTenantId int64) (db gdb.DB, tenant *entity.ImTenant, err error) {
|
|
scope, err := resolveImAdminScope(ctx, selectedTenantId)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return imtenant.DB(ctx, scope.TenantId)
|
|
}
|
|
|
|
func writeImAdminAudit(ctx context.Context, tenantId int64, action string, targetType string, targetId int64, result string, detail g.Map) error {
|
|
operatorId := contexts.GetUserId(ctx)
|
|
request := ghttp.RequestFromCtx(ctx)
|
|
ip := ""
|
|
if request != nil {
|
|
ip = request.GetClientIp()
|
|
}
|
|
detailJson := gjson.New(detail)
|
|
_, err := g.DB().Exec(ctx,
|
|
"INSERT INTO "+imAdminOperationAuditTable+" (request_id, tenant_id, operator_id, action, target_type, target_id, result, ip, detail_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?)",
|
|
gctx.CtxId(ctx),
|
|
tenantId,
|
|
operatorId,
|
|
action,
|
|
targetType,
|
|
targetId,
|
|
result,
|
|
ip,
|
|
detailJson.String(),
|
|
gtime.Now(),
|
|
)
|
|
if err != nil && !simple.IsDemo(ctx) {
|
|
g.Log().Warningf(ctx, "写入IM后台审计失败 action:%s err:%+v", action, err)
|
|
}
|
|
return err
|
|
}
|