Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 14 additions & 20 deletions api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -8702,12 +8702,6 @@
},
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
},
"DateTime": {
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
"type": "string",
"format": "date-time",
"x-go-package": "github.com/go-openapi/strfmt"
},
"DbServiceConnections": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -9355,7 +9349,7 @@
"x-go-name": "Message"
}
},
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
},
"GetOauth2ConfigurationResData": {
"type": "object",
Expand Down Expand Up @@ -10006,6 +10000,10 @@
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
},
"I18nStr": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/i18nPkg"
},
"ImportDBService": {
Expand Down Expand Up @@ -11891,17 +11889,6 @@
"ListMemberRoleWithOpRange": {
"type": "object",
"properties": {
"member_group": {
"$ref": "#/definitions/ProjectMemberGroup"
},
"op_permissions": {
"description": "member op permissions",
"type": "array",
"items": {
"$ref": "#/definitions/UidWithName"
},
"x-go-name": "OpPermissions"
},
"op_range_type": {
"description": "op permission range type, only support db service now\nunknown OpRangeTypeUnknown\nglobal OpRangeTypeGlobal 全局权限: 该权限只能被用户使用\nproject OpRangeTypeProject 项目权限: 该权限只能被成员使用\ndb_service OpRangeTypeDBService 项目内的数据源权限: 该权限只能被成员使用",
"type": "string",
Expand All @@ -11926,7 +11913,7 @@
"$ref": "#/definitions/UidWithName"
}
},
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
},
"ListMemberTipsItem": {
"type": "object",
Expand Down Expand Up @@ -13862,6 +13849,13 @@
"type": "boolean",
"x-go-name": "AuditEnabled"
},
"maintenance_times": {
"type": "array",
"items": {
"$ref": "#/definitions/MaintenanceTime"
},
"x-go-name": "MaintenanceTimes"
},
"max_pre_query_rows": {
"type": "integer",
"format": "int64",
Expand Down Expand Up @@ -14311,7 +14305,7 @@
"x-go-name": "Uid"
}
},
"x-go-package": "github.com/actiontech/dms/api/dms/service/v1"
"x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1"
},
"UpdateBusinessTagReq": {
"type": "object",
Expand Down
31 changes: 11 additions & 20 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1389,15 +1389,6 @@ definitions:
x-go-name: Params
type: object
x-go-package: github.com/actiontech/dms/api/dms/service/v1
DateTime:
description: |-
DateTime is a time but it serializes to ISO8601 format with millis
It knows how to read 3 different variations of a RFC3339 date time.
Most APIs we encounter want either millisecond or second precision times.
This just tries to make it worry-free.
format: date-time
type: string
x-go-package: github.com/go-openapi/strfmt
DbServiceConnections:
properties:
db_service_uid:
Expand Down Expand Up @@ -1889,7 +1880,7 @@ definitions:
type: string
x-go-name: Message
type: object
x-go-package: github.com/actiontech/dms/api/dms/service/v1
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
GetOauth2ConfigurationResData:
properties:
access_token_tag:
Expand Down Expand Up @@ -2417,6 +2408,9 @@ definitions:
type: object
x-go-package: github.com/actiontech/dms/api/dms/service/v1
I18nStr:
additionalProperties:
type: string
type: object
x-go-package: github.com/actiontech/dms/pkg/dms-common/i18nPkg
ImportDBService:
properties:
Expand Down Expand Up @@ -3891,14 +3885,6 @@ definitions:
x-go-package: github.com/actiontech/dms/api/dms/service/v1
ListMemberRoleWithOpRange:
properties:
member_group:
$ref: '#/definitions/ProjectMemberGroup'
op_permissions:
description: member op permissions
items:
$ref: '#/definitions/UidWithName'
type: array
x-go-name: OpPermissions
op_range_type:
description: |-
op permission range type, only support db service now
Expand Down Expand Up @@ -3927,7 +3913,7 @@ definitions:
role_uid:
$ref: '#/definitions/UidWithName'
type: object
x-go-package: github.com/actiontech/dms/api/dms/service/v1
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
ListMemberTipsItem:
properties:
user_id:
Expand Down Expand Up @@ -5599,6 +5585,11 @@ definitions:
audit_enabled:
type: boolean
x-go-name: AuditEnabled
maintenance_times:
items:
$ref: '#/definitions/MaintenanceTime'
type: array
x-go-name: MaintenanceTimes
max_pre_query_rows:
format: int64
type: integer
Expand Down Expand Up @@ -5928,7 +5919,7 @@ definitions:
type: string
x-go-name: Uid
type: object
x-go-package: github.com/actiontech/dms/api/dms/service/v1
x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1
UpdateBusinessTagReq:
properties:
business_tag:
Expand Down
49 changes: 48 additions & 1 deletion internal/dms/biz/cloudbeaver.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ type CloudbeaverUsecase struct {
projectUsecase *ProjectUsecase
repo CloudbeaverRepo
proxyTargetRepo ProxyTargetRepo
maintenanceTimeUsecase *MaintenanceTimeUsecase
}

func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, dataMaskingUseCase *DataMaskingUsecase, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseDase *CbOperationLogUsecase, projectUsecase *ProjectUsecase) (cu *CloudbeaverUsecase) {
func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, dataMaskingUseCase *DataMaskingUsecase, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseDase *CbOperationLogUsecase, projectUsecase *ProjectUsecase, maintenanceTimeUsecase *MaintenanceTimeUsecase) (cu *CloudbeaverUsecase) {
cu = &CloudbeaverUsecase{
repo: cloudbeaverRepo,
proxyTargetRepo: proxyTargetRepo,
Expand All @@ -104,6 +105,7 @@ func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase
projectUsecase: projectUsecase,
cloudbeaverCfg: cfg,
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.cloudbeaver")),
maintenanceTimeUsecase: maintenanceTimeUsecase,
}

// 启动缓存清理协程
Expand Down Expand Up @@ -629,6 +631,11 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc {
return nil, c.JSON(http.StatusOK, convertToResp(ctx, resp))
}

// [运维时间管控检查] 在审核通过后、工单判断前检查运维时间管控
if blocked, err := cu.checkMaintenanceTime(c, resp.Results, dbService); blocked || err != nil {
return nil, err
}

// 判断是否需要通过工单执行(非 DQL 语句)
if cu.shouldExecuteByWorkflow(dbService, resp.Results) {
return cu.executeNonDQLByWorkflow(ctx, c, dbService, params, resp)
Expand Down Expand Up @@ -2067,6 +2074,46 @@ func (cu *CloudbeaverUsecase) shouldExecuteByWorkflow(dbService *DBService, audi
return false
}

// checkMaintenanceTime 检查运维时间管控(CloudBeaver工作台)
// 返回 blocked=true 表示已构造拦截响应,调用方应立即返回
func (cu *CloudbeaverUsecase) checkMaintenanceTime(c echo.Context, auditResults []cloudbeaver.AuditSQLResV2, dbService *DBService) (blocked bool, err error) {
if cu.maintenanceTimeUsecase == nil {
return false, fmt.Errorf("maintenance time usecase is nil")
}

currentUserUid, _ := c.Get(dmsUserIdKey).(string)
if currentUserUid == "" {
return false, fmt.Errorf("current user uid is empty")
}

sqlTypes := make([]string, 0, len(auditResults))
for _, r := range auditResults {
sqlTypes = append(sqlTypes, r.SQLType)
}

var sqlQueryConfig *SQLQueryConfig
if dbService != nil && dbService.SQLEConfig != nil {
sqlQueryConfig = dbService.SQLEConfig.SQLQueryConfig
}

allowed, message, checkErr := cu.maintenanceTimeUsecase.CheckSQLExecutionAllowed(
c.Request().Context(),
currentUserUid,
sqlTypes,
time.Now(),
sqlQueryConfig,
)
if checkErr != nil {
cu.log.Errorf("check maintenance time failed: %v", checkErr)
return false, checkErr
}
if !allowed {
return true, c.JSON(http.StatusOK,
newResp(c.Request().Context(), "Maintenance Time Blocked", CBErrorCode, message))
}
return false, nil
}

// workflowExecParams 工单执行所需的参数
type workflowExecParams struct {
contextIdStr string
Expand Down
15 changes: 8 additions & 7 deletions internal/dms/biz/db_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,14 @@ type BizDBServiceArgs struct {
}

type SQLQueryConfig struct {
MaxPreQueryRows int `json:"max_pre_query_rows"`
QueryTimeoutSecond int `json:"query_timeout_second"`
AuditEnabled bool `json:"audit_enabled"`
WorkflowExecEnabled bool `json:"workflow_exec_enabled"`
AllowQueryWhenLessThanAuditLevel string `json:"allow_query_when_less_than_audit_level"`
RuleTemplateID string `json:"rule_template_id"`
RuleTemplateName string `json:"rule_template_name"`
MaxPreQueryRows int `json:"max_pre_query_rows"`
QueryTimeoutSecond int `json:"query_timeout_second"`
AuditEnabled bool `json:"audit_enabled"`
WorkflowExecEnabled bool `json:"workflow_exec_enabled"`
AllowQueryWhenLessThanAuditLevel string `json:"allow_query_when_less_than_audit_level"`
RuleTemplateID string `json:"rule_template_id"`
RuleTemplateName string `json:"rule_template_name"`
MaintenancePeriods pkgPeriods.Periods `json:"maintenance_periods"`
}

func (d *DBServiceUsecase) CreateDBService(ctx context.Context, args *BizDBServiceArgs, currentUserUid string) (uid string, err error) {
Expand Down
121 changes: 121 additions & 0 deletions internal/dms/biz/maintenance_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package biz

import (
"context"
"fmt"
"strings"
"time"

"github.com/actiontech/dms/internal/pkg/locale"
pkgPeriods "github.com/actiontech/dms/pkg/periods"

utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log"
)

// MaintenanceTimeConfig 运维时间管控配置
type MaintenanceTimeConfig struct {
Enabled bool
Periods pkgPeriods.Periods
}

// MaintenanceTimeUsecase 运维时间管控业务逻辑
type MaintenanceTimeUsecase struct {
opPermissionVerifyUsecase *OpPermissionVerifyUsecase
log *utilLog.Helper
}

// NewMaintenanceTimeUsecase 创建运维时间管控业务逻辑实例
func NewMaintenanceTimeUsecase(
log utilLog.Logger,
opPermissionVerifyUsecase *OpPermissionVerifyUsecase,
) *MaintenanceTimeUsecase {
return &MaintenanceTimeUsecase{
opPermissionVerifyUsecase: opPermissionVerifyUsecase,
log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.maintenance_time")),
}
}

// MaintenanceTimeConfigFromSQLQuery 从数据源 sql_query_config 解析运维时间管控配置
func MaintenanceTimeConfigFromSQLQuery(sqlQueryConfig *SQLQueryConfig) *MaintenanceTimeConfig {
cfg := &MaintenanceTimeConfig{}
if sqlQueryConfig == nil {
return cfg
}
cfg.Enabled = len(sqlQueryConfig.MaintenancePeriods) > 0
if cfg.Enabled {
cfg.Periods = sqlQueryConfig.MaintenancePeriods.Copy()
}
return cfg
}

// CheckSQLExecutionAllowed 检查SQL执行是否被运维时间管控允许
// 参数:
// - userUid: 当前执行SQL的用户UID
// - sqlTypes: SQLE审核返回的sql_type列表(每条SQL对应一个)
// - currentTime: 当前时间(参数化便于测试)
// - sqlQueryConfig: 数据源上的 SQL 查询配置(含运维时间窗口)
//
// 返回值:
// - allowed: 是否允许执行
// - message: 拦截时的提示消息(allowed=true时为空)
// - err: 内部错误
func (m *MaintenanceTimeUsecase) CheckSQLExecutionAllowed(
ctx context.Context,
userUid string,
sqlTypes []string,
currentTime time.Time,
sqlQueryConfig *SQLQueryConfig,
) (allowed bool, message string, err error) {
// 1. 获取配置
config := MaintenanceTimeConfigFromSQLQuery(sqlQueryConfig)

// 2. 如果开关关闭,直接允许执行
if !config.Enabled {
return true, "", nil
}

// 3. 检查 sqlTypes 中是否有非DQL语句
hasNonDQL := false
for _, sqlType := range sqlTypes {
if sqlType != "dql" { // 空字符串("")也视为非DQL(保守策略)
hasNonDQL = true
break
}
}
if !hasNonDQL {
return true, "", nil
}

// 4. 检查用户是否为管理员
isAdmin, err := m.opPermissionVerifyUsecase.CanOpGlobal(ctx, userUid)
if err != nil {
return false, "", fmt.Errorf("failed to check user admin permission: %v", err)
}
if isAdmin {
m.log.Warnf("user %s is admin, skip cloudbeaver maintenance time check", userUid)
return true, "", nil
}

// 5. 检查当前时间是否在配置的运维时间段内
if config.Periods.IsWithinScope(currentTime) {
return true, "", nil
}

// 6. 构造拦截消息
periodsStr := formatPeriodsToReadableString(config.Periods)
message = fmt.Sprintf(locale.Bundle.LocalizeMsgByCtx(ctx, locale.SqlWorkbenchMaintenanceTimeBlocked), periodsStr)

// 7. 返回拦截结果
return false, message, nil
}

// formatPeriodsToReadableString 将时间段格式化为可读字符串
// 例如: "01:00-06:00, 22:00-02:00"
func formatPeriodsToReadableString(ps pkgPeriods.Periods) string {
parts := make([]string, 0, len(ps))
for _, p := range ps {
parts = append(parts, fmt.Sprintf("%02d:%02d-%02d:%02d",
p.StartHour, p.StartMinute, p.EndHour, p.EndMinute))
}
return strings.Join(parts, ", ")
}
Loading