diff --git a/Makefile b/Makefile index 824691b8a..ffee44a8c 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,10 @@ dms_unit_test_clean: dms_test_dms: go test -v -p 1 ./internal/dms/... +# 提交前校验:社区版 / 试用版 / 企业版 / DMS 企业版 四种 GO_BUILD_TAGS 组合下均能 make install +verify_edition_builds: + bash ./scripts/verify_build_editions.sh + ############################### generate ################################## gen_repo_fields: go run ./internal/dms/cmd/gencli/gencli.go -d generate-node-repo-fields ./internal/dms/storage/model/ ./internal/dms/biz/ diff --git a/api/dms/service/v1/data_export_task.go b/api/dms/service/v1/data_export_task.go index 37b8eb5ae..498616d88 100644 --- a/api/dms/service/v1/data_export_task.go +++ b/api/dms/service/v1/data_export_task.go @@ -3,6 +3,7 @@ package v1 import ( "time" + maskingBiz "github.com/actiontech/dms/internal/data_masking/biz" base "github.com/actiontech/dms/pkg/dms-common/api/base/v1" ) @@ -127,6 +128,10 @@ type ListDataExportTaskSQL struct { ExportSQLType string `json:"export_sql_type"` AuditLevel string `json:"audit_level"` AuditSQLResult []AuditSQLResult `json:"audit_sql_result"` + // 血缘分析快照(与查看原文工单 SQL 详情字段语义一致) + LineageAnalysisSnapshot *maskingBiz.AnalyzeResult `json:"lineage_analysis_snapshot,omitempty"` + // 脱敏配置快照 + MaskingConfigSnapshot []*maskingBiz.ColumnMaskingConfig `json:"masking_config_snapshot,omitempty"` } type AuditSQLResult struct { Level string `json:"level" example:"warn"` diff --git a/api/dms/service/v1/data_export_workflow.go b/api/dms/service/v1/data_export_workflow.go index 3e3c81519..f4d2d6d12 100644 --- a/api/dms/service/v1/data_export_workflow.go +++ b/api/dms/service/v1/data_export_workflow.go @@ -3,6 +3,7 @@ package v1 import ( "time" + "github.com/actiontech/dms/internal/data_masking/biz" base "github.com/actiontech/dms/pkg/dms-common/api/base/v1" dmsCommonV1 "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" ) @@ -221,6 +222,31 @@ type GetDataExportWorkflow struct { CreateTime *time.Time `json:"create_time"` WorkflowRecord WorkflowRecord `json:"workflow_record"` WorkflowRecordHistory []WorkflowRecord `json:"workflow_record_history"` + // UnmaskingWorkflow 关联的查看原文工单摘要;无关联时为 null + UnmaskingWorkflow *DataExportRelatedUnmaskingWorkflow `json:"unmasking_workflow"` +} + +// swagger:model DataExportRelatedUnmaskingWorkflow +// DataExportRelatedUnmaskingWorkflow 数据导出工单关联的查看原文工单(仅摘要字段) +type DataExportRelatedUnmaskingWorkflow struct { + // UnmaskingWorkflowUid 查看原文工单 UID,用于跳转详情等 + UnmaskingWorkflowUid string `json:"unmasking_workflow_uid"` + // 创建人(uid + 展示名) + Creator UidWithName `json:"creator"` + // 创建时间 (RFC3339) + CreatedAt string `json:"created_at"` + // 过期时间 (RFC3339),未设置时省略 + ExpireTime string `json:"expire_time,omitempty"` + // 审批状态 + ApprovalStatus biz.UnmaskingWorkflowApprovalStatus `json:"approval_status"` + // 使用状态 + UsageStatus biz.UnmaskingWorkflowUsageStatus `json:"usage_status"` + // 申请理由 + ApplyReason string `json:"apply_reason"` + // 驳回理由(整单驳回时) + RejectReason string `json:"reject_reason,omitempty"` + // 操作记录 + OperationLogs []*UnmaskingOperationLogItem `json:"operation_logs"` } type WorkflowRecord struct { @@ -267,6 +293,28 @@ type ExportDataExportWorkflowReq struct { DataExportWorkflowUid string `param:"data_export_workflow_uid" json:"data_export_workflow_uid" validate:"required"` } +// swagger:parameters DownloadOriginalDataExportWorkflow +type DownloadOriginalDataExportWorkflowReq struct { + // project id + // Required: true + // in:path + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // Required: true + // in:path + DataExportWorkflowUid string `param:"data_export_workflow_uid" json:"data_export_workflow_uid" validate:"required"` + // 已批准的查看原文工单 UID + // Required: true + // in:query + UnmaskingWorkflowUid string `query:"unmasking_workflow_uid" json:"unmasking_workflow_uid" validate:"required"` +} + +// swagger:response DownloadOriginalDataExportWorkflowReply +type DownloadOriginalDataExportWorkflowReply struct { + // swagger:file + // in: body + File []byte +} + type RejectDataExportWorkflowPayload struct { // Required: true Reason string `json:"reason" validate:"required"` diff --git a/api/dms/service/v1/db_structure_columns.go b/api/dms/service/v1/db_structure_columns.go new file mode 100644 index 000000000..632e694d6 --- /dev/null +++ b/api/dms/service/v1/db_structure_columns.go @@ -0,0 +1,36 @@ +package v1 + +import ( + base "github.com/actiontech/dms/pkg/dms-common/api/base/v1" +) + +// swagger:parameters ListTableColumns +type ListTableColumnsReq struct { + // Required: true + // in:path + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // Required: true + // in:path + DBServiceUid string `param:"db_service_uid" json:"db_service_uid" validate:"required"` + // Required: true + // in:path + SchemaName string `param:"schema_name" json:"schema_name" validate:"required"` + // Required: true + // in:path + TableName string `param:"table_name" json:"table_name" validate:"required"` +} + +// swagger:model ListTableColumnsReply +type ListTableColumnsReply struct { + Data []*TableColumn `json:"data"` + // Generic reply + base.GenericResp +} + +// swagger:model TableColumn +type TableColumn struct { + Name string `json:"name"` + Type string `json:"type"` + Comment string `json:"comment"` + Nullable bool `json:"nullable"` +} diff --git a/api/dms/service/v1/unmasking_workflow.go b/api/dms/service/v1/unmasking_workflow.go new file mode 100644 index 000000000..6cc067741 --- /dev/null +++ b/api/dms/service/v1/unmasking_workflow.go @@ -0,0 +1,316 @@ +package v1 + +import ( + "github.com/actiontech/dms/internal/data_masking/biz" + base "github.com/actiontech/dms/pkg/dms-common/api/base/v1" +) + +// swagger:parameters ListUnmaskingWorkflows +type ListUnmaskingWorkflowsReq struct { + // project id + // Required: true + // in: path + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // the maximum count of workflows to be returned + // in: query + // Required: true + PageSize uint32 `query:"page_size" json:"page_size" validate:"required"` + // the offset of workflows to be returned, default is 0 + // in: query + PageIndex uint32 `query:"page_index" json:"page_index"` + // filter the approval status + // in: query + FilterByApprovalStatus biz.UnmaskingWorkflowApprovalStatus `query:"filter_by_approval_status" json:"filter_by_approval_status"` + // filter the usage status + // in: query + FilterByUsageStatus biz.UnmaskingWorkflowUsageStatus `query:"filter_by_usage_status" json:"filter_by_usage_status"` + // filter db_service id + // in: query + FilterByDBServiceUid string `query:"filter_by_db_service_uid" json:"filter_by_db_service_uid"` +} + +// swagger:model ListUnmaskingWorkflowsReply +type ListUnmaskingWorkflowsReply struct { + Data []*UnmaskingWorkflowListItem `json:"data"` + Total int64 `json:"total_nums"` + // Generic reply + base.GenericResp +} + +// swagger:model UnmaskingWorkflowListItem +type UnmaskingWorkflowListItem struct { + // 申请编号 + WorkflowID string `json:"workflow_id"` + // 申请人用户名 + ApplicantName string `json:"applicant_name"` + // 申请时间 (RFC3339) + CreatedAt string `json:"created_at" example:"2024-01-15T10:30:00Z"` + // 数据源实例名称 + DatasourceName string `json:"datasource_name"` + // 数据源实例ID + DatasourceUid string `json:"datasource_uid"` + // 来源类型 + SourceType biz.UnmaskingWorkflowSourceType `json:"source_type" validate:"oneof=data_export sql_workbench"` + // 来源对象UID + SourceUID string `json:"source_uid"` + // 审批状态 + ApprovalStatus biz.UnmaskingWorkflowApprovalStatus `json:"approval_status" validate:"oneof=pending approved rejected cancelled"` + // 使用情况 + UsageStatus biz.UnmaskingWorkflowUsageStatus `json:"usage_status" validate:"oneof=unviewed viewed"` + // 过期时间 (RFC3339),兼容字段:批准前为激活截止,激活后为查看截止 + ExpireTime string `json:"expire_time" example:"2024-01-16T10:30:00Z"` + // 激活查看时刻 (RFC3339) + ActivatedAt string `json:"activated_at"` + // 须在此时刻前激活查看 (RFC3339) + ActivationDeadline string `json:"activation_deadline"` + // 明文查看截止 (RFC3339) + ViewValidUntil string `json:"view_valid_until"` + // 申请人明文查看状态 + ViewState biz.UnmaskingWorkflowViewState `json:"view_state"` + // 是否可点击激活查看 + CanActivate bool `json:"can_activate"` + // 申请理由 + ApplyReason string `json:"apply_reason"` + // 当前待处理人 + CurrentAssignees []*UidWithName `json:"current_assignees"` +} + +// swagger:model CreateUnmaskingWorkflowReq +type CreateUnmaskingWorkflowReq struct { + // swagger:ignore + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: body + // Required: true + UnmaskingWorkflow *CreateUnmaskingWorkflow `json:"unmasking_workflow" validate:"required"` +} + +// swagger:model CreateUnmaskingWorkflow +type CreateUnmaskingWorkflow struct { + // 数据源 UID + DatasourceUID string `json:"datasource_uid" validate:"required"` + // SQL 默认 schema + DefaultSchema string `json:"default_schema" validate:"required"` + // 来源类型 + SourceType biz.UnmaskingWorkflowSourceType `json:"source_type" validate:"required,oneof=data_export sql_workbench"` + // 来源对象 UID (如数据导出任务 UID) + SourceUID string `json:"source_uid"` + // 申请理由 + ApplyReason string `json:"apply_reason" validate:"required"` + // 待脱敏 SQL 列表 + UnmaskingSQLs []CreateUnmaskingSQLItem `json:"unmasking_sqls" validate:"required,gt=0"` +} + +// swagger:model CreateUnmaskingSQLItem +type CreateUnmaskingSQLItem struct { + // 来源侧 SQL 索引 id(如数据导出记录中的 SQL 序号);与 SQL 工作台场景的索引约定可能不同,需结合 source_type、source_uid 解析 + SQLIndexID string `json:"sql_index_id" validate:"required"` + // 原始 SQL 内容 + SQLContent string `json:"sql_content" validate:"required"` +} + +// swagger:model CreateUnmaskingWorkflowReply +type CreateUnmaskingWorkflowReply struct { + Data *CreateUnmaskingWorkflowReplyData `json:"data"` + // Generic reply + base.GenericResp +} + +// swagger:model CreateUnmaskingWorkflowReplyData +type CreateUnmaskingWorkflowReplyData struct { + WorkflowID string `json:"workflow_id"` +} + +// swagger:parameters GetUnmaskingWorkflow +type GetUnmaskingWorkflowReq struct { + // project id + // Required: true + // in: path + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` +} + +// swagger:model GetUnmaskingWorkflowReply +type GetUnmaskingWorkflowReply struct { + Data *UnmaskingWorkflowDetail `json:"data"` + // Generic reply + base.GenericResp +} + +// swagger:model UnmaskingWorkflowDetail +type UnmaskingWorkflowDetail struct { + UnmaskingWorkflowListItem + // 驳回理由 (整单驳回时) + RejectReason string `json:"reject_reason"` + // 当前待处理人 + CurrentAssignees []*UidWithName `json:"current_assignees"` + // SQL 详情列表 + UnmaskingSQLs []*UnmaskingSQLDetail `json:"unmasking_sqls"` + // 操作日志 + OperationLogs []*UnmaskingOperationLogItem `json:"operation_logs"` +} + +// swagger:model UnmaskingSQLDetail +type UnmaskingSQLDetail struct { + // SQL 详情 UID + UID string `json:"uid"` + // 来源侧 SQL 索引 id + SQLIndexID string `json:"sql_index_id"` + // 原始 SQL 内容 + SQLContent string `json:"sql_content"` + // 脱敏配置快照 + MaskingConfigSnapshot []*biz.ColumnMaskingConfig `json:"masking_config_snapshot,omitempty"` + // 血缘分析快照 + LineageAnalysisSnapshot *biz.AnalyzeResult `json:"lineage_analysis_snapshot,omitempty"` + + // 脱敏后的预览数据 (普通用户仅能看到此数据) + MaskedData *SQLQueryResult `json:"masked_data"` + // 原始采样数据 (仅有权限的审核人能看到) + OriginalData *SQLQueryResult `json:"original_data"` +} + +// swagger:model SQLQueryResultRow +// SQLQueryResultRow 一行数据;每个元素为单元格的字符串形式(与 columns 顺序一致)。 +type SQLQueryResultRow []string + +// swagger:model SQLQueryResult +type SQLQueryResult struct { + // 列名列表 + Columns []string `json:"columns"` + // 数据行列表 (每一行的数据顺序与 Columns 一致) + Rows []SQLQueryResultRow `json:"rows"` + // 总行数 + RowCount int `json:"row_count"` +} + +// swagger:model UnmaskingOperationLogItem +type UnmaskingOperationLogItem struct { + // 操作人 UID + OperatorUID string `json:"operator_uid"` + // 操作人姓名 + OperatorName string `json:"operator_name"` + // 操作动作 + Action biz.UnmaskingAction `json:"action"` + // 操作时间 (RFC3339) + ActionTime string `json:"action_time"` + // 额外信息 (如拦截原因) + ExtraMessage string `json:"extra_message"` +} + +type ApproveUnmaskingWorkflowReq struct { + // swagger:ignore + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + // swagger:ignore + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` + // in: body + ApproveUnmaskingWorkflow *ApproveUnmaskingWorkflow `json:"approve_unmasking_workflow,omitempty"` +} + +// swagger:model ApproveUnmaskingWorkflow +type ApproveUnmaskingWorkflow struct { + // 审批理由 非必须 + ApproveReason string `json:"approve_reason"` +} + +// swagger:model ApproveUnmaskingWorkflowReply +type ApproveUnmaskingWorkflowReply struct { + // Generic reply + base.GenericResp +} + +type RejectUnmaskingWorkflowReq struct { + // swagger:ignore + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + // swagger:ignore + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` + // in: body + // Required: true + RejectUnmaskingWorkflow *RejectUnmaskingWorkflow `json:"reject_unmasking_workflow" validate:"required"` +} + +// swagger:model RejectUnmaskingWorkflow +type RejectUnmaskingWorkflow struct { + // 驳回理由 + // Required: true + RejectReason string `json:"reject_reason" validate:"required"` +} + +// swagger:model RejectUnmaskingWorkflowReply +type RejectUnmaskingWorkflowReply struct { + // Generic reply + base.GenericResp +} + +// swagger:parameters CancelUnmaskingWorkflow +type CancelUnmaskingWorkflowReq struct { + // project id + // Required: true + // in: path + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` +} + +// swagger:model CancelUnmaskingWorkflowReply +type CancelUnmaskingWorkflowReply struct { + // Generic reply + base.GenericResp +} + +// swagger:parameters ActivateUnmaskingWorkflowView +type ActivateUnmaskingWorkflowViewReq struct { + // swagger:ignore + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` +} + +// swagger:model ActivateUnmaskingWorkflowViewReply +type ActivateUnmaskingWorkflowViewReply struct { + Data *ActivateUnmaskingWorkflowViewReplyData `json:"data"` + base.GenericResp +} + +// swagger:model ActivateUnmaskingWorkflowViewReplyData +type ActivateUnmaskingWorkflowViewReplyData struct { + ViewValidUntil string `json:"view_valid_until"` + ViewState biz.UnmaskingWorkflowViewState `json:"view_state"` +} + +// swagger:parameters GetUnmaskingWorkflowPlaintext +type GetUnmaskingWorkflowPlaintextReq struct { + // swagger:ignore + ProjectUid string `param:"project_uid" json:"project_uid" validate:"required"` + // in: path + // Required: true + WorkflowID string `param:"workflow_id" json:"workflow_id" validate:"required"` +} + +// swagger:model GetUnmaskingWorkflowPlaintextReply +type GetUnmaskingWorkflowPlaintextReply struct { + Data *GetUnmaskingWorkflowPlaintext `json:"data"` + base.GenericResp +} + +// swagger:model GetUnmaskingWorkflowPlaintext +type GetUnmaskingWorkflowPlaintext struct { + ViewState biz.UnmaskingWorkflowViewState `json:"view_state"` + ViewValidUntil string `json:"view_valid_until"` + UnmaskingSQLs []*UnmaskingPlaintextSQLItem `json:"unmasking_sqls"` +} + +// swagger:model UnmaskingPlaintextSQLItem +type UnmaskingPlaintextSQLItem struct { + UID string `json:"uid"` + SQLIndexID string `json:"sql_index_id"` + OriginalData *SQLQueryResult `json:"original_data"` + MaskedColumns []string `json:"masked_columns"` + Truncated bool `json:"truncated"` +} diff --git a/api/swagger.json b/api/swagger.json index e0e7b566e..725c05257 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -4353,150 +4353,6 @@ } } }, - "/v1/dms/projects/{project_uid}/masking/approval-requests/pending": { - "get": { - "tags": [ - "Masking" - ], - "summary": "查询待审批申请列表。", - "operationId": "ListPendingApprovalRequests", - "parameters": [ - { - "type": "string", - "example": "\"project_uid\"", - "x-go-name": "ProjectUid", - "description": "project uid", - "name": "project_uid", - "in": "path", - "required": true - }, - { - "type": "integer", - "format": "uint32", - "example": "20", - "x-go-name": "PageSize", - "description": "the maximum count of requests to be returned, default is 20", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "format": "uint32", - "example": "0", - "x-go-name": "PageIndex", - "description": "the offset of requests to be returned, default is 0", - "name": "page_index", - "in": "query" - } - ], - "responses": { - "200": { - "description": "ListPendingApprovalRequestsReply", - "schema": { - "$ref": "#/definitions/ListPendingApprovalRequestsReply" - } - }, - "default": { - "description": "GenericResp", - "schema": { - "$ref": "#/definitions/GenericResp" - } - } - } - } - }, - "/v1/dms/projects/{project_uid}/masking/approval-requests/{request_id}": { - "get": { - "tags": [ - "Masking" - ], - "summary": "获取明文访问申请详情。", - "operationId": "GetPlaintextAccessRequestDetail", - "parameters": [ - { - "type": "string", - "example": "\"project_uid\"", - "x-go-name": "ProjectUid", - "description": "project uid", - "name": "project_uid", - "in": "path", - "required": true - }, - { - "type": "integer", - "format": "int64", - "example": 1, - "x-go-name": "RequestID", - "description": "approval request id", - "name": "request_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "GetPlaintextAccessRequestDetailReply", - "schema": { - "$ref": "#/definitions/GetPlaintextAccessRequestDetailReply" - } - }, - "default": { - "description": "GenericResp", - "schema": { - "$ref": "#/definitions/GenericResp" - } - } - } - } - }, - "/v1/dms/projects/{project_uid}/masking/approval-requests/{request_id}/decisions": { - "post": { - "tags": [ - "Masking" - ], - "summary": "处理审批申请。", - "operationId": "ProcessApprovalRequest", - "parameters": [ - { - "type": "string", - "description": "项目 UID", - "name": "project_uid", - "in": "path", - "required": true - }, - { - "type": "integer", - "description": "审批申请 ID", - "name": "request_id", - "in": "path", - "required": true - }, - { - "description": "处理动作信息", - "name": "action", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/ProcessApprovalRequestReq" - } - } - ], - "responses": { - "200": { - "description": "成功处理审批申请", - "schema": { - "$ref": "#/definitions/ProcessApprovalRequestReply" - } - }, - "default": { - "description": "通用错误响应", - "schema": { - "$ref": "#/definitions/GenericResp" - } - } - } - } - }, "/v1/dms/projects/{project_uid}/masking/overview": { "get": { "tags": [ @@ -5713,6 +5569,305 @@ } } }, + "/v1/dms/projects/{project_uid}/masking/unmasking-workflows": { + "get": { + "tags": [ + "Masking" + ], + "summary": "List unmasking workflows.", + "operationId": "ListUnmaskingWorkflows", + "parameters": [ + { + "type": "string", + "x-go-name": "ProjectUid", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "type": "integer", + "format": "uint32", + "x-go-name": "PageSize", + "description": "the maximum count of workflows to be returned", + "name": "page_size", + "in": "query", + "required": true + }, + { + "type": "integer", + "format": "uint32", + "x-go-name": "PageIndex", + "description": "the offset of workflows to be returned, default is 0", + "name": "page_index", + "in": "query" + }, + { + "enum": [ + "pending", + "approved", + "rejected", + "cancelled" + ], + "type": "string", + "x-go-enum-desc": "pending UnmaskingWorkflowApprovalStatusPending 待审批\napproved UnmaskingWorkflowApprovalStatusApproved 已批准\nrejected UnmaskingWorkflowApprovalStatusRejected 已驳回\ncancelled UnmaskingWorkflowApprovalStatusCancelled 已取消", + "x-go-name": "FilterByApprovalStatus", + "description": "filter the approval status", + "name": "filter_by_approval_status", + "in": "query" + }, + { + "enum": [ + "unviewed", + "viewed" + ], + "type": "string", + "x-go-enum-desc": "unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看\nviewed UnmaskingWorkflowUsageStatusViewed 已查看", + "x-go-name": "FilterByUsageStatus", + "description": "filter the usage status", + "name": "filter_by_usage_status", + "in": "query" + }, + { + "type": "string", + "x-go-name": "FilterByDBServiceUid", + "description": "filter db_service id", + "name": "filter_by_db_service_uid", + "in": "query" + } + ], + "responses": { + "200": { + "description": "List unmasking workflows successfully", + "schema": { + "$ref": "#/definitions/ListUnmaskingWorkflowsReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + }, + "post": { + "tags": [ + "Masking" + ], + "summary": "Create unmasking workflow.", + "operationId": "CreateUnmaskingWorkflow", + "parameters": [ + { + "type": "string", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "description": "unmasking workflow info", + "name": "unmasking_workflow", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateUnmaskingWorkflowReq" + } + } + ], + "responses": { + "200": { + "description": "Create unmasking workflow successfully", + "schema": { + "$ref": "#/definitions/CreateUnmaskingWorkflowReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + } + }, + "/v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}": { + "get": { + "tags": [ + "Masking" + ], + "summary": "Get unmasking workflow detail.", + "operationId": "GetUnmaskingWorkflow", + "parameters": [ + { + "type": "string", + "x-go-name": "ProjectUid", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "WorkflowID", + "description": "workflow id", + "name": "workflow_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Get unmasking workflow detail successfully", + "schema": { + "$ref": "#/definitions/GetUnmaskingWorkflowReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + } + }, + "/v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/approve": { + "post": { + "tags": [ + "Masking" + ], + "summary": "Approve unmasking workflow.", + "operationId": "ApproveUnmaskingWorkflow", + "parameters": [ + { + "type": "string", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "workflow id", + "name": "workflow_id", + "in": "path", + "required": true + }, + { + "description": "approve unmasking workflow info (optional, only carries approve_reason)", + "name": "approve_unmasking_workflow", + "in": "body", + "schema": { + "$ref": "#/definitions/ApproveUnmaskingWorkflow" + } + } + ], + "responses": { + "200": { + "description": "Approve unmasking workflow successfully", + "schema": { + "$ref": "#/definitions/ApproveUnmaskingWorkflowReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + } + }, + "/v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/cancel": { + "post": { + "tags": [ + "Masking" + ], + "summary": "Cancel unmasking workflow.", + "operationId": "CancelUnmaskingWorkflow", + "parameters": [ + { + "type": "string", + "x-go-name": "ProjectUid", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "WorkflowID", + "description": "workflow id", + "name": "workflow_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Cancel unmasking workflow successfully", + "schema": { + "$ref": "#/definitions/CancelUnmaskingWorkflowReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + } + }, + "/v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/reject": { + "post": { + "tags": [ + "Masking" + ], + "summary": "Reject unmasking workflow.", + "operationId": "RejectUnmaskingWorkflow", + "parameters": [ + { + "type": "string", + "description": "project id", + "name": "project_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "workflow id", + "name": "workflow_id", + "in": "path", + "required": true + }, + { + "description": "reject unmasking workflow info", + "name": "reject_unmasking_workflow", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/RejectUnmaskingWorkflow" + } + } + ], + "responses": { + "200": { + "description": "Reject unmasking workflow successfully", + "schema": { + "$ref": "#/definitions/RejectUnmaskingWorkflowReply" + } + }, + "default": { + "description": "Generic error response", + "schema": { + "$ref": "#/definitions/GenericResp" + } + } + } + } + }, "/v1/dms/projects/{project_uid}/member_groups": { "get": { "tags": [ @@ -9051,21 +9206,156 @@ "message": { "description": "message", "type": "string", - "x-go-name": "Message" + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "AddUserGroupReq": { + "type": "object", + "properties": { + "user_group": { + "$ref": "#/definitions/UserGroup" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "AddUserReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "data": { + "description": "Add user reply", + "type": "object", + "properties": { + "uid": { + "description": "user UID", + "type": "string", + "x-go-name": "Uid" + } + }, + "x-go-name": "Data" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "AddUserReq": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/User" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "AdditionalParam": { + "type": "object", + "properties": { + "description": { + "type": "string", + "x-go-name": "Description" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "type": { + "type": "string", + "x-go-name": "Type" + }, + "value": { + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" + }, + "AnalyzeResult": { + "type": "object", + "properties": { + "edges": { + "description": "血缘图边", + "type": "array", + "items": { + "$ref": "#/definitions/LineageEdge" + }, + "x-go-name": "Edges" + }, + "nodes": { + "description": "血缘图节点", + "type": "array", + "items": { + "$ref": "#/definitions/LineageNode" + }, + "x-go-name": "Nodes" + }, + "original_sql": { + "description": "原始 SQL", + "type": "string", + "x-go-name": "OriginalSQL" + }, + "result_columns": { + "description": "解析到的结果列列表", + "type": "array", + "items": { + "$ref": "#/definitions/ResultColumn" + }, + "x-go-name": "ResultColumns" + }, + "source_columns": { + "description": "解析到的源列列表", + "type": "array", + "items": { + "$ref": "#/definitions/ColumnRef" + }, + "x-go-name": "SourceColumns" + }, + "tables": { + "description": "解析到的表引用列表", + "type": "array", + "items": { + "$ref": "#/definitions/TableRef" + }, + "x-go-name": "Tables" + }, + "title": { + "description": "分析标题/摘要", + "type": "string", + "x-go-name": "Title" + }, + "warnings": { + "description": "警告信息(解析不完整等)", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Warnings" } }, - "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" }, - "AddUserGroupReq": { + "ApproveUnmaskingWorkflow": { "type": "object", "properties": { - "user_group": { - "$ref": "#/definitions/UserGroup" + "approve_reason": { + "description": "审批理由 非必须", + "type": "string", + "x-go-name": "ApproveReason" } }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, - "AddUserReply": { + "ApproveUnmaskingWorkflowReply": { "type": "object", "properties": { "code": { @@ -9074,18 +9364,6 @@ "format": "int64", "x-go-name": "Code" }, - "data": { - "description": "Add user reply", - "type": "object", - "properties": { - "uid": { - "description": "user UID", - "type": "string", - "x-go-name": "Uid" - } - }, - "x-go-name": "Data" - }, "message": { "description": "message", "type": "string", @@ -9094,37 +9372,6 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, - "AddUserReq": { - "type": "object", - "properties": { - "user": { - "$ref": "#/definitions/User" - } - }, - "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" - }, - "AdditionalParam": { - "type": "object", - "properties": { - "description": { - "type": "string", - "x-go-name": "Description" - }, - "name": { - "type": "string", - "x-go-name": "Name" - }, - "type": { - "type": "string", - "x-go-name": "Type" - }, - "value": { - "type": "string", - "x-go-name": "Value" - } - }, - "x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" - }, "AuditPlanTypes": { "type": "object", "properties": { @@ -9450,6 +9697,23 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "CancelUnmaskingWorkflowReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "CheckDBServiceIsConnectableByIdReq": { "type": "object", "properties": { @@ -9738,6 +10002,116 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "ColumnMaskingConfig": { + "description": "ColumnMaskingConfig 列级别脱敏配置领域模型", + "type": "object", + "properties": { + "column_id": { + "description": "列 ID(db_columns.id)", + "type": "integer", + "format": "uint64", + "x-go-name": "ColumnID" + }, + "column_name": { + "description": "列名", + "type": "string", + "x-go-name": "ColumnName" + }, + "confidence": { + "description": "置信度\nHigh ConfidenceHigh 高:高度确信为敏感数据\nMedium ConfidenceMedium 中:中等确信为敏感数据\nLow ConfidenceLow 低:低确信为敏感数据", + "type": "string", + "enum": [ + "High", + "Medium", + "Low" + ], + "x-go-enum-desc": "High ConfidenceHigh 高:高度确信为敏感数据\nMedium ConfidenceMedium 中:中等确信为敏感数据\nLow ConfidenceLow 低:低确信为敏感数据", + "x-go-name": "Confidence" + }, + "created_at": { + "description": "创建时间", + "type": "string", + "format": "date-time", + "x-go-name": "CreatedAt" + }, + "db_service_uid": { + "description": "数据源 UID", + "type": "string", + "x-go-name": "DBServiceUID" + }, + "id": { + "description": "配置记录 ID", + "type": "integer", + "format": "uint64", + "x-go-name": "ID" + }, + "is_masking_enabled": { + "description": "是否启用脱敏", + "type": "boolean", + "x-go-name": "IsMaskingEnabled" + }, + "masking_rule_id": { + "description": "脱敏规则 ID", + "type": "integer", + "format": "int64", + "x-go-name": "MaskingRuleID" + }, + "masking_rule_name": { + "description": "脱敏规则名称(中文)", + "type": "string", + "x-go-name": "MaskingRuleName" + }, + "schema_name": { + "description": "Schema 名称", + "type": "string", + "x-go-name": "SchemaName" + }, + "status": { + "description": "配置状态\nPENDING_CONFIRM MaskingConfigStatusPendingConfirm\nCONFIGURED MaskingConfigStatusConfigured\nSYSTEM_CONFIRMED MaskingConfigStatusSystemConfirmed", + "type": "string", + "enum": [ + "PENDING_CONFIRM", + "CONFIGURED", + "SYSTEM_CONFIRMED" + ], + "x-go-enum-desc": "PENDING_CONFIRM MaskingConfigStatusPendingConfirm\nCONFIGURED MaskingConfigStatusConfigured\nSYSTEM_CONFIRMED MaskingConfigStatusSystemConfirmed", + "x-go-name": "Status" + }, + "table_name": { + "description": "表名", + "type": "string", + "x-go-name": "TableName" + }, + "updated_at": { + "description": "更新时间", + "type": "string", + "format": "date-time", + "x-go-name": "UpdatedAt" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" + }, + "ColumnRef": { + "type": "object", + "properties": { + "column": { + "description": "列名", + "type": "string", + "x-go-name": "Column" + }, + "schema": { + "description": "Schema 名称", + "type": "string", + "x-go-name": "Schema" + }, + "table": { + "description": "表名", + "type": "string", + "x-go-name": "Table" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" + }, "CompanyNotice": { "description": "A companynotice", "type": "object", @@ -9840,6 +10214,108 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "CreateUnmaskingSQLItem": { + "type": "object", + "properties": { + "sql_content": { + "description": "原始 SQL 内容", + "type": "string", + "x-go-name": "SQLContent" + }, + "sql_index_id": { + "description": "来源侧 SQL 索引 id(如数据导出记录中的 SQL 序号);与 SQL 工作台场景的索引约定可能不同,需结合 source_type、source_uid 解析", + "type": "string", + "x-go-name": "SQLIndexID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "CreateUnmaskingWorkflow": { + "type": "object", + "properties": { + "apply_reason": { + "description": "申请理由", + "type": "string", + "x-go-name": "ApplyReason" + }, + "datasource_uid": { + "description": "数据源 UID", + "type": "string", + "x-go-name": "DatasourceUID" + }, + "default_schema": { + "description": "SQL 默认 schema", + "type": "string", + "x-go-name": "DefaultSchema" + }, + "source_type": { + "description": "来源类型\ndata_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "type": "string", + "enum": [ + "data_export", + "sql_workbench" + ], + "x-go-enum-desc": "data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "x-go-name": "SourceType" + }, + "source_uid": { + "description": "来源对象 UID (如数据导出任务 UID)", + "type": "string", + "x-go-name": "SourceUID" + }, + "unmasking_sqls": { + "description": "待脱敏 SQL 列表", + "type": "array", + "items": { + "$ref": "#/definitions/CreateUnmaskingSQLItem" + }, + "x-go-name": "UnmaskingSQLs" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "CreateUnmaskingWorkflowReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "data": { + "$ref": "#/definitions/CreateUnmaskingWorkflowReplyData" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "CreateUnmaskingWorkflowReplyData": { + "type": "object", + "properties": { + "workflow_id": { + "type": "string", + "x-go-name": "WorkflowID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "CreateUnmaskingWorkflowReq": { + "type": "object", + "required": [ + "unmasking_workflow" + ], + "properties": { + "unmasking_workflow": { + "$ref": "#/definitions/CreateUnmaskingWorkflow" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "CurrentProjectAdmin": { "type": "object", "properties": { @@ -11608,6 +12084,26 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "GetUnmaskingWorkflowReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "data": { + "$ref": "#/definitions/UnmaskingWorkflowDetail" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "GetUser": { "description": "A dms user", "type": "object", @@ -12421,7 +12917,82 @@ "x-go-name": "Used" } }, - "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "LineageEdge": { + "type": "object", + "properties": { + "from_id": { + "description": "起点节点 ID", + "type": "string", + "x-go-name": "FromID" + }, + "to_id": { + "description": "终点节点 ID", + "type": "string", + "x-go-name": "ToID" + }, + "type": { + "description": "边类型\ndirect EdgeTypeDirect 直接依赖\ntransform EdgeTypeTransform 转换/计算依赖\naggregate EdgeTypeAggregate 聚合依赖", + "type": "string", + "enum": [ + "direct", + "transform", + "aggregate" + ], + "x-go-enum-desc": "direct EdgeTypeDirect 直接依赖\ntransform EdgeTypeTransform 转换/计算依赖\naggregate EdgeTypeAggregate 聚合依赖", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" + }, + "LineageNode": { + "type": "object", + "properties": { + "column": { + "description": "列名(列节点可能存在)", + "type": "string", + "x-go-name": "Column" + }, + "expr": { + "description": "表达式内容(表达式节点可能存在)", + "type": "string", + "x-go-name": "Expr" + }, + "id": { + "description": "节点 ID(图内唯一)", + "type": "string", + "x-go-name": "ID" + }, + "name": { + "description": "节点展示名", + "type": "string", + "x-go-name": "Name" + }, + "schema": { + "description": "Schema 名称(列节点可能存在)", + "type": "string", + "x-go-name": "Schema" + }, + "table": { + "description": "表名(列节点可能存在)", + "type": "string", + "x-go-name": "Table" + }, + "type": { + "description": "节点类型\nsource_column NodeTypeSource 源列节点\nexpression NodeTypeExpression 表达式节点\nresult_column NodeTypeResult 结果列节点\ntable NodeTypeTable 表节点", + "type": "string", + "enum": [ + "source_column", + "expression", + "result_column", + "table" + ], + "x-go-enum-desc": "source_column NodeTypeSource 源列节点\nexpression NodeTypeExpression 表达式节点\nresult_column NodeTypeResult 结果列节点\ntable NodeTypeTable 表节点", + "x-go-name": "Type" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" }, "ListBusinessTagsReply": { "type": "object", @@ -13159,6 +13730,17 @@ "type": "string", "x-go-name": "ExportSQLType" }, + "lineage_analysis_snapshot": { + "$ref": "#/definitions/AnalyzeResult" + }, + "masking_config_snapshot": { + "description": "脱敏配置快照", + "type": "array", + "items": { + "$ref": "#/definitions/ColumnMaskingConfig" + }, + "x-go-name": "MaskingConfigSnapshot" + }, "sql": { "type": "string", "x-go-name": "ExportSQL" @@ -14829,6 +15411,35 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "ListUnmaskingWorkflowsReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/UnmaskingWorkflowListItem" + }, + "x-go-name": "Data" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + }, + "total_nums": { + "type": "integer", + "format": "int64", + "x-go-name": "Total" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "ListUser": { "description": "A dms user", "type": "object", @@ -16444,6 +17055,37 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "RejectUnmaskingWorkflow": { + "type": "object", + "required": [ + "reject_reason" + ], + "properties": { + "reject_reason": { + "description": "驳回理由", + "type": "string", + "x-go-name": "RejectReason" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "RejectUnmaskingWorkflowReply": { + "type": "object", + "properties": { + "code": { + "description": "code", + "type": "integer", + "format": "int64", + "x-go-name": "Code" + }, + "message": { + "description": "message", + "type": "string", + "x-go-name": "Message" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "ResourceBusiness": { "type": "object", "properties": { @@ -16694,6 +17336,30 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "ResultColumn": { + "type": "object", + "properties": { + "expression": { + "description": "结果列表达式(SQL 片段)", + "type": "string", + "x-go-name": "Expression" + }, + "name": { + "description": "结果列名", + "type": "string", + "x-go-name": "Name" + }, + "sources": { + "description": "来源列列表", + "type": "array", + "items": { + "$ref": "#/definitions/ColumnRef" + }, + "x-go-name": "Sources" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" + }, "Role": { "description": "A role", "type": "object", @@ -16832,6 +17498,41 @@ }, "x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" }, + "SQLQueryResult": { + "type": "object", + "properties": { + "columns": { + "description": "列名列表", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Columns" + }, + "row_count": { + "description": "总行数", + "type": "integer", + "format": "int64", + "x-go-name": "RowCount" + }, + "rows": { + "description": "数据行列表 (每一行的数据顺序与 Columns 一致)", + "type": "array", + "items": { + "$ref": "#/definitions/SQLQueryResultRow" + }, + "x-go-name": "Rows" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "SQLQueryResultRow": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "SendSmsCodeReply": { "type": "object", "properties": { @@ -17173,6 +17874,27 @@ }, "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" }, + "TableRef": { + "type": "object", + "properties": { + "alias": { + "description": "表别名", + "type": "string", + "x-go-name": "Alias" + }, + "schema": { + "description": "Schema 名称", + "type": "string", + "x-go-name": "Schema" + }, + "table": { + "description": "表名", + "type": "string", + "x-go-name": "Table" + } + }, + "x-go-package": "github.com/actiontech/dms/internal/data_masking/biz" + }, "Task": { "type": "object", "properties": { @@ -17589,6 +18311,278 @@ }, "x-go-package": "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" }, + "UnmaskingOperationLogItem": { + "type": "object", + "properties": { + "action": { + "description": "操作动作\nsubmit UnmaskingActionSubmit 提交申请\napprove UnmaskingActionApprove 批准申请\nreject UnmaskingActionReject 驳回申请\nview_unmasking_workflow_detail UnmaskingActionViewOriginalDataWorkflowDetail 查看工单详情\nview_full_original_data UnmaskingActionViewOriginalData 查看原文\ndownload_full_original_data UnmaskingActionDownloadOriginalData 下载原文\ncancel UnmaskingActionCancel 取消申请", + "type": "string", + "enum": [ + "submit", + "approve", + "reject", + "view_unmasking_workflow_detail", + "view_full_original_data", + "download_full_original_data", + "cancel" + ], + "x-go-enum-desc": "submit UnmaskingActionSubmit 提交申请\napprove UnmaskingActionApprove 批准申请\nreject UnmaskingActionReject 驳回申请\nview_unmasking_workflow_detail UnmaskingActionViewOriginalDataWorkflowDetail 查看工单详情\nview_full_original_data UnmaskingActionViewOriginalData 查看原文\ndownload_full_original_data UnmaskingActionDownloadOriginalData 下载原文\ncancel UnmaskingActionCancel 取消申请", + "x-go-name": "Action" + }, + "action_time": { + "description": "操作时间 (RFC3339)", + "type": "string", + "x-go-name": "ActionTime" + }, + "extra_message": { + "description": "额外信息 (如拦截原因)", + "type": "string", + "x-go-name": "ExtraMessage" + }, + "operator_name": { + "description": "操作人姓名", + "type": "string", + "x-go-name": "OperatorName" + }, + "operator_uid": { + "description": "操作人 UID", + "type": "string", + "x-go-name": "OperatorUID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "UnmaskingSQLDetail": { + "type": "object", + "properties": { + "lineage_analysis_snapshot": { + "$ref": "#/definitions/AnalyzeResult" + }, + "masked_data": { + "$ref": "#/definitions/SQLQueryResult" + }, + "masking_config_snapshot": { + "description": "脱敏配置快照", + "type": "array", + "items": { + "$ref": "#/definitions/ColumnMaskingConfig" + }, + "x-go-name": "MaskingConfigSnapshot" + }, + "original_data": { + "$ref": "#/definitions/SQLQueryResult" + }, + "sql_content": { + "description": "原始 SQL 内容", + "type": "string", + "x-go-name": "SQLContent" + }, + "sql_index_id": { + "description": "来源侧 SQL 索引 id", + "type": "string", + "x-go-name": "SQLIndexID" + }, + "uid": { + "description": "SQL 详情 UID", + "type": "string", + "x-go-name": "UID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "UnmaskingWorkflowDetail": { + "type": "object", + "properties": { + "applicant_name": { + "description": "申请人用户名", + "type": "string", + "x-go-name": "ApplicantName" + }, + "apply_reason": { + "description": "申请理由", + "type": "string", + "x-go-name": "ApplyReason" + }, + "approval_status": { + "description": "审批状态\npending UnmaskingWorkflowApprovalStatusPending 待审批\napproved UnmaskingWorkflowApprovalStatusApproved 已批准\nrejected UnmaskingWorkflowApprovalStatusRejected 已驳回\ncancelled UnmaskingWorkflowApprovalStatusCancelled 已取消", + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "cancelled" + ], + "x-go-enum-desc": "pending UnmaskingWorkflowApprovalStatusPending 待审批\napproved UnmaskingWorkflowApprovalStatusApproved 已批准\nrejected UnmaskingWorkflowApprovalStatusRejected 已驳回\ncancelled UnmaskingWorkflowApprovalStatusCancelled 已取消", + "x-go-name": "ApprovalStatus" + }, + "created_at": { + "description": "申请时间 (RFC3339)", + "type": "string", + "x-go-name": "CreatedAt" + }, + "current_assignees": { + "description": "当前待处理人", + "type": "array", + "items": { + "$ref": "#/definitions/UidWithName" + }, + "x-go-name": "CurrentAssignees" + }, + "datasource_name": { + "description": "数据源实例名称", + "type": "string", + "x-go-name": "DatasourceName" + }, + "datasource_uid": { + "description": "数据源实例ID", + "type": "string", + "x-go-name": "DatasourceUid" + }, + "expire_time": { + "description": "过期时间 (RFC3339)", + "type": "string", + "x-go-name": "ExpireTime" + }, + "operation_logs": { + "description": "操作日志", + "type": "array", + "items": { + "$ref": "#/definitions/UnmaskingOperationLogItem" + }, + "x-go-name": "OperationLogs" + }, + "reject_reason": { + "description": "驳回理由 (整单驳回时)", + "type": "string", + "x-go-name": "RejectReason" + }, + "source_type": { + "description": "来源类型\ndata_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "type": "string", + "enum": [ + "data_export", + "sql_workbench" + ], + "x-go-enum-desc": "data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "x-go-name": "SourceType" + }, + "source_uid": { + "description": "来源对象UID", + "type": "string", + "x-go-name": "SourceUID" + }, + "unmasking_sqls": { + "description": "SQL 详情列表", + "type": "array", + "items": { + "$ref": "#/definitions/UnmaskingSQLDetail" + }, + "x-go-name": "UnmaskingSQLs" + }, + "usage_status": { + "description": "使用情况\nunviewed UnmaskingWorkflowUsageStatusUnviewed 未查看\nviewed UnmaskingWorkflowUsageStatusViewed 已查看", + "type": "string", + "enum": [ + "unviewed", + "viewed" + ], + "x-go-enum-desc": "unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看\nviewed UnmaskingWorkflowUsageStatusViewed 已查看", + "x-go-name": "UsageStatus" + }, + "workflow_id": { + "description": "申请编号", + "type": "string", + "x-go-name": "WorkflowID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, + "UnmaskingWorkflowListItem": { + "type": "object", + "properties": { + "applicant_name": { + "description": "申请人用户名", + "type": "string", + "x-go-name": "ApplicantName" + }, + "apply_reason": { + "description": "申请理由", + "type": "string", + "x-go-name": "ApplyReason" + }, + "approval_status": { + "description": "审批状态\npending UnmaskingWorkflowApprovalStatusPending 待审批\napproved UnmaskingWorkflowApprovalStatusApproved 已批准\nrejected UnmaskingWorkflowApprovalStatusRejected 已驳回\ncancelled UnmaskingWorkflowApprovalStatusCancelled 已取消", + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "cancelled" + ], + "x-go-enum-desc": "pending UnmaskingWorkflowApprovalStatusPending 待审批\napproved UnmaskingWorkflowApprovalStatusApproved 已批准\nrejected UnmaskingWorkflowApprovalStatusRejected 已驳回\ncancelled UnmaskingWorkflowApprovalStatusCancelled 已取消", + "x-go-name": "ApprovalStatus" + }, + "created_at": { + "description": "申请时间 (RFC3339)", + "type": "string", + "x-go-name": "CreatedAt" + }, + "current_assignees": { + "description": "当前待处理人", + "type": "array", + "items": { + "$ref": "#/definitions/UidWithName" + }, + "x-go-name": "CurrentAssignees" + }, + "datasource_name": { + "description": "数据源实例名称", + "type": "string", + "x-go-name": "DatasourceName" + }, + "datasource_uid": { + "description": "数据源实例ID", + "type": "string", + "x-go-name": "DatasourceUid" + }, + "expire_time": { + "description": "过期时间 (RFC3339)", + "type": "string", + "x-go-name": "ExpireTime" + }, + "source_type": { + "description": "来源类型\ndata_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "type": "string", + "enum": [ + "data_export", + "sql_workbench" + ], + "x-go-enum-desc": "data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单\nsql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台", + "x-go-name": "SourceType" + }, + "source_uid": { + "description": "来源对象UID", + "type": "string", + "x-go-name": "SourceUID" + }, + "usage_status": { + "description": "使用情况\nunviewed UnmaskingWorkflowUsageStatusUnviewed 未查看\nviewed UnmaskingWorkflowUsageStatusViewed 已查看", + "type": "string", + "enum": [ + "unviewed", + "viewed" + ], + "x-go-enum-desc": "unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看\nviewed UnmaskingWorkflowUsageStatusViewed 已查看", + "x-go-name": "UsageStatus" + }, + "workflow_id": { + "description": "申请编号", + "type": "string", + "x-go-name": "WorkflowID" + } + }, + "x-go-package": "github.com/actiontech/dms/api/dms/service/v1" + }, "UpdateBusinessTagReq": { "type": "object", "properties": { diff --git a/api/swagger.yaml b/api/swagger.yaml index 70d66e2ba..2444ad1c3 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -688,6 +688,75 @@ definitions: x-go-name: Value type: object x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1 + AnalyzeResult: + properties: + edges: + description: 血缘图边 + items: + $ref: '#/definitions/LineageEdge' + type: array + x-go-name: Edges + nodes: + description: 血缘图节点 + items: + $ref: '#/definitions/LineageNode' + type: array + x-go-name: Nodes + original_sql: + description: 原始 SQL + type: string + x-go-name: OriginalSQL + result_columns: + description: 解析到的结果列列表 + items: + $ref: '#/definitions/ResultColumn' + type: array + x-go-name: ResultColumns + source_columns: + description: 解析到的源列列表 + items: + $ref: '#/definitions/ColumnRef' + type: array + x-go-name: SourceColumns + tables: + description: 解析到的表引用列表 + items: + $ref: '#/definitions/TableRef' + type: array + x-go-name: Tables + title: + description: 分析标题/摘要 + type: string + x-go-name: Title + warnings: + description: 警告信息(解析不完整等) + items: + type: string + type: array + x-go-name: Warnings + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz + ApproveUnmaskingWorkflow: + properties: + approve_reason: + description: 审批理由 非必须 + type: string + x-go-name: ApproveReason + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + ApproveUnmaskingWorkflowReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + message: + description: message + type: string + x-go-name: Message + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 AuditPlanTypes: properties: audit_plan_id: @@ -925,6 +994,19 @@ definitions: $ref: '#/definitions/CancelDataExportWorkflowPayload' type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CancelUnmaskingWorkflowReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + message: + description: message + type: string + x-go-name: Message + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 CheckDBServiceIsConnectableByIdReq: properties: db_service_uid: @@ -1143,6 +1225,108 @@ definitions: x-go-name: Message type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + ColumnMaskingConfig: + description: ColumnMaskingConfig 列级别脱敏配置领域模型 + properties: + column_id: + description: 列 ID(db_columns.id) + format: uint64 + type: integer + x-go-name: ColumnID + column_name: + description: 列名 + type: string + x-go-name: ColumnName + confidence: + description: |- + 置信度 + High ConfidenceHigh 高:高度确信为敏感数据 + Medium ConfidenceMedium 中:中等确信为敏感数据 + Low ConfidenceLow 低:低确信为敏感数据 + enum: + - High + - Medium + - Low + type: string + x-go-enum-desc: |- + High ConfidenceHigh 高:高度确信为敏感数据 + Medium ConfidenceMedium 中:中等确信为敏感数据 + Low ConfidenceLow 低:低确信为敏感数据 + x-go-name: Confidence + created_at: + description: 创建时间 + format: date-time + type: string + x-go-name: CreatedAt + db_service_uid: + description: 数据源 UID + type: string + x-go-name: DBServiceUID + id: + description: 配置记录 ID + format: uint64 + type: integer + x-go-name: ID + is_masking_enabled: + description: 是否启用脱敏 + type: boolean + x-go-name: IsMaskingEnabled + masking_rule_id: + description: 脱敏规则 ID + format: int64 + type: integer + x-go-name: MaskingRuleID + masking_rule_name: + description: 脱敏规则名称(中文) + type: string + x-go-name: MaskingRuleName + schema_name: + description: Schema 名称 + type: string + x-go-name: SchemaName + status: + description: |- + 配置状态 + PENDING_CONFIRM MaskingConfigStatusPendingConfirm + CONFIGURED MaskingConfigStatusConfigured + SYSTEM_CONFIRMED MaskingConfigStatusSystemConfirmed + enum: + - PENDING_CONFIRM + - CONFIGURED + - SYSTEM_CONFIRMED + type: string + x-go-enum-desc: |- + PENDING_CONFIRM MaskingConfigStatusPendingConfirm + CONFIGURED MaskingConfigStatusConfigured + SYSTEM_CONFIRMED MaskingConfigStatusSystemConfirmed + x-go-name: Status + table_name: + description: 表名 + type: string + x-go-name: TableName + updated_at: + description: 更新时间 + format: date-time + type: string + x-go-name: UpdatedAt + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz + ColumnRef: + properties: + column: + description: 列名 + type: string + x-go-name: Column + schema: + description: Schema 名称 + type: string + x-go-name: Schema + table: + description: 表名 + type: string + x-go-name: Table + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz CompanyNotice: description: A companynotice properties: @@ -1219,6 +1403,87 @@ definitions: x-go-name: Name type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CreateUnmaskingSQLItem: + properties: + sql_content: + description: 原始 SQL 内容 + type: string + x-go-name: SQLContent + sql_index_id: + description: 来源侧 SQL 索引 id(如数据导出记录中的 SQL 序号);与 SQL 工作台场景的索引约定可能不同,需结合 source_type、source_uid 解析 + type: string + x-go-name: SQLIndexID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CreateUnmaskingWorkflow: + properties: + apply_reason: + description: 申请理由 + type: string + x-go-name: ApplyReason + datasource_uid: + description: 数据源 UID + type: string + x-go-name: DatasourceUID + default_schema: + description: SQL 默认 schema + type: string + x-go-name: DefaultSchema + source_type: + description: |- + 来源类型 + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + enum: + - data_export + - sql_workbench + type: string + x-go-enum-desc: |- + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + x-go-name: SourceType + source_uid: + description: 来源对象 UID (如数据导出任务 UID) + type: string + x-go-name: SourceUID + unmasking_sqls: + description: 待脱敏 SQL 列表 + items: + $ref: '#/definitions/CreateUnmaskingSQLItem' + type: array + x-go-name: UnmaskingSQLs + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CreateUnmaskingWorkflowReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + data: + $ref: '#/definitions/CreateUnmaskingWorkflowReplyData' + message: + description: message + type: string + x-go-name: Message + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CreateUnmaskingWorkflowReplyData: + properties: + workflow_id: + type: string + x-go-name: WorkflowID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + CreateUnmaskingWorkflowReq: + properties: + unmasking_workflow: + $ref: '#/definitions/CreateUnmaskingWorkflow' + required: + - unmasking_workflow + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 CurrentProjectAdmin: properties: is_admin: @@ -2575,6 +2840,21 @@ definitions: x-go-name: Message type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + GetUnmaskingWorkflowReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + data: + $ref: '#/definitions/UnmaskingWorkflowDetail' + message: + description: message + type: string + x-go-name: Message + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 GetUser: description: A dms user properties: @@ -3228,6 +3508,81 @@ definitions: x-go-name: Used type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + LineageEdge: + properties: + from_id: + description: 起点节点 ID + type: string + x-go-name: FromID + to_id: + description: 终点节点 ID + type: string + x-go-name: ToID + type: + description: |- + 边类型 + direct EdgeTypeDirect 直接依赖 + transform EdgeTypeTransform 转换/计算依赖 + aggregate EdgeTypeAggregate 聚合依赖 + enum: + - direct + - transform + - aggregate + type: string + x-go-enum-desc: |- + direct EdgeTypeDirect 直接依赖 + transform EdgeTypeTransform 转换/计算依赖 + aggregate EdgeTypeAggregate 聚合依赖 + x-go-name: Type + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz + LineageNode: + properties: + column: + description: 列名(列节点可能存在) + type: string + x-go-name: Column + expr: + description: 表达式内容(表达式节点可能存在) + type: string + x-go-name: Expr + id: + description: 节点 ID(图内唯一) + type: string + x-go-name: ID + name: + description: 节点展示名 + type: string + x-go-name: Name + schema: + description: Schema 名称(列节点可能存在) + type: string + x-go-name: Schema + table: + description: 表名(列节点可能存在) + type: string + x-go-name: Table + type: + description: |- + 节点类型 + source_column NodeTypeSource 源列节点 + expression NodeTypeExpression 表达式节点 + result_column NodeTypeResult 结果列节点 + table NodeTypeTable 表节点 + enum: + - source_column + - expression + - result_column + - table + type: string + x-go-enum-desc: |- + source_column NodeTypeSource 源列节点 + expression NodeTypeExpression 表达式节点 + result_column NodeTypeResult 结果列节点 + table NodeTypeTable 表节点 + x-go-name: Type + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz ListBusinessTagsReply: properties: code: @@ -3811,6 +4166,14 @@ definitions: export_sql_type: type: string x-go-name: ExportSQLType + lineage_analysis_snapshot: + $ref: '#/definitions/AnalyzeResult' + masking_config_snapshot: + description: 脱敏配置快照 + items: + $ref: '#/definitions/ColumnMaskingConfig' + type: array + x-go-name: MaskingConfigSnapshot sql: type: string x-go-name: ExportSQL @@ -5228,6 +5591,28 @@ definitions: x-go-name: Message type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + ListUnmaskingWorkflowsReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + data: + items: + $ref: '#/definitions/UnmaskingWorkflowListItem' + type: array + x-go-name: Data + message: + description: message + type: string + x-go-name: Message + total_nums: + format: int64 + type: integer + x-go-name: Total + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 ListUser: description: A dms user properties: @@ -6639,6 +7024,29 @@ definitions: $ref: '#/definitions/RejectDataExportWorkflowPayload' type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + RejectUnmaskingWorkflow: + properties: + reject_reason: + description: 驳回理由 + type: string + x-go-name: RejectReason + required: + - reject_reason + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + RejectUnmaskingWorkflowReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + message: + description: message + type: string + x-go-name: Message + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 ResourceBusiness: properties: business_tag: @@ -6829,6 +7237,24 @@ definitions: x-go-name: ResourceType type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + ResultColumn: + properties: + expression: + description: 结果列表达式(SQL 片段) + type: string + x-go-name: Expression + name: + description: 结果列名 + type: string + x-go-name: Name + sources: + description: 来源列列表 + items: + $ref: '#/definitions/ColumnRef' + type: array + x-go-name: Sources + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz Role: description: A role properties: @@ -6937,18 +7363,44 @@ definitions: x-go-name: WorkflowExecEnabled type: object x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1 - SendSmsCodeReply: + SQLQueryResult: properties: - code: - description: code + columns: + description: 列名列表 + items: + type: string + type: array + x-go-name: Columns + row_count: + description: 总行数 format: int64 type: integer - x-go-name: Code - data: - $ref: '#/definitions/SendSmsCodeReplyData' - message: - description: message - type: string + x-go-name: RowCount + rows: + description: 数据行列表 (每一行的数据顺序与 Columns 一致) + items: + $ref: '#/definitions/SQLQueryResultRow' + type: array + x-go-name: Rows + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + SQLQueryResultRow: + items: + type: string + type: array + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + SendSmsCodeReply: + properties: + code: + description: code + format: int64 + type: integer + x-go-name: Code + data: + $ref: '#/definitions/SendSmsCodeReplyData' + message: + description: message + type: string x-go-name: Message type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 @@ -7236,6 +7688,22 @@ definitions: x-go-name: Status type: object x-go-package: github.com/actiontech/dms/api/dms/service/v1 + TableRef: + properties: + alias: + description: 表别名 + type: string + x-go-name: Alias + schema: + description: Schema 名称 + type: string + x-go-name: Schema + table: + description: 表名 + type: string + x-go-name: Table + type: object + x-go-package: github.com/actiontech/dms/internal/data_masking/biz Task: properties: task_uid: @@ -7540,6 +8008,272 @@ definitions: x-go-name: Uid type: object x-go-package: github.com/actiontech/dms/pkg/dms-common/api/dms/v1 + UnmaskingOperationLogItem: + properties: + action: + description: |- + 操作动作 + submit UnmaskingActionSubmit 提交申请 + approve UnmaskingActionApprove 批准申请 + reject UnmaskingActionReject 驳回申请 + view_unmasking_workflow_detail UnmaskingActionViewOriginalDataWorkflowDetail 查看工单详情 + view_full_original_data UnmaskingActionViewOriginalData 查看原文 + download_full_original_data UnmaskingActionDownloadOriginalData 下载原文 + cancel UnmaskingActionCancel 取消申请 + enum: + - submit + - approve + - reject + - view_unmasking_workflow_detail + - view_full_original_data + - download_full_original_data + - cancel + type: string + x-go-enum-desc: |- + submit UnmaskingActionSubmit 提交申请 + approve UnmaskingActionApprove 批准申请 + reject UnmaskingActionReject 驳回申请 + view_unmasking_workflow_detail UnmaskingActionViewOriginalDataWorkflowDetail 查看工单详情 + view_full_original_data UnmaskingActionViewOriginalData 查看原文 + download_full_original_data UnmaskingActionDownloadOriginalData 下载原文 + cancel UnmaskingActionCancel 取消申请 + x-go-name: Action + action_time: + description: 操作时间 (RFC3339) + type: string + x-go-name: ActionTime + extra_message: + description: 额外信息 (如拦截原因) + type: string + x-go-name: ExtraMessage + operator_name: + description: 操作人姓名 + type: string + x-go-name: OperatorName + operator_uid: + description: 操作人 UID + type: string + x-go-name: OperatorUID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + UnmaskingSQLDetail: + properties: + lineage_analysis_snapshot: + $ref: '#/definitions/AnalyzeResult' + masked_data: + $ref: '#/definitions/SQLQueryResult' + masking_config_snapshot: + description: 脱敏配置快照 + items: + $ref: '#/definitions/ColumnMaskingConfig' + type: array + x-go-name: MaskingConfigSnapshot + original_data: + $ref: '#/definitions/SQLQueryResult' + sql_content: + description: 原始 SQL 内容 + type: string + x-go-name: SQLContent + sql_index_id: + description: 来源侧 SQL 索引 id + type: string + x-go-name: SQLIndexID + uid: + description: SQL 详情 UID + type: string + x-go-name: UID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + UnmaskingWorkflowDetail: + properties: + applicant_name: + description: 申请人用户名 + type: string + x-go-name: ApplicantName + apply_reason: + description: 申请理由 + type: string + x-go-name: ApplyReason + approval_status: + description: |- + 审批状态 + pending UnmaskingWorkflowApprovalStatusPending 待审批 + approved UnmaskingWorkflowApprovalStatusApproved 已批准 + rejected UnmaskingWorkflowApprovalStatusRejected 已驳回 + cancelled UnmaskingWorkflowApprovalStatusCancelled 已取消 + enum: + - pending + - approved + - rejected + - cancelled + type: string + x-go-enum-desc: |- + pending UnmaskingWorkflowApprovalStatusPending 待审批 + approved UnmaskingWorkflowApprovalStatusApproved 已批准 + rejected UnmaskingWorkflowApprovalStatusRejected 已驳回 + cancelled UnmaskingWorkflowApprovalStatusCancelled 已取消 + x-go-name: ApprovalStatus + created_at: + description: 申请时间 (RFC3339) + type: string + x-go-name: CreatedAt + current_assignees: + description: 当前待处理人 + items: + $ref: '#/definitions/UidWithName' + type: array + x-go-name: CurrentAssignees + datasource_name: + description: 数据源实例名称 + type: string + x-go-name: DatasourceName + datasource_uid: + description: 数据源实例ID + type: string + x-go-name: DatasourceUid + expire_time: + description: 过期时间 (RFC3339) + type: string + x-go-name: ExpireTime + operation_logs: + description: 操作日志 + items: + $ref: '#/definitions/UnmaskingOperationLogItem' + type: array + x-go-name: OperationLogs + reject_reason: + description: 驳回理由 (整单驳回时) + type: string + x-go-name: RejectReason + source_type: + description: |- + 来源类型 + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + enum: + - data_export + - sql_workbench + type: string + x-go-enum-desc: |- + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + x-go-name: SourceType + source_uid: + description: 来源对象UID + type: string + x-go-name: SourceUID + unmasking_sqls: + description: SQL 详情列表 + items: + $ref: '#/definitions/UnmaskingSQLDetail' + type: array + x-go-name: UnmaskingSQLs + usage_status: + description: |- + 使用情况 + unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看 + viewed UnmaskingWorkflowUsageStatusViewed 已查看 + enum: + - unviewed + - viewed + type: string + x-go-enum-desc: |- + unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看 + viewed UnmaskingWorkflowUsageStatusViewed 已查看 + x-go-name: UsageStatus + workflow_id: + description: 申请编号 + type: string + x-go-name: WorkflowID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 + UnmaskingWorkflowListItem: + properties: + applicant_name: + description: 申请人用户名 + type: string + x-go-name: ApplicantName + apply_reason: + description: 申请理由 + type: string + x-go-name: ApplyReason + approval_status: + description: |- + 审批状态 + pending UnmaskingWorkflowApprovalStatusPending 待审批 + approved UnmaskingWorkflowApprovalStatusApproved 已批准 + rejected UnmaskingWorkflowApprovalStatusRejected 已驳回 + cancelled UnmaskingWorkflowApprovalStatusCancelled 已取消 + enum: + - pending + - approved + - rejected + - cancelled + type: string + x-go-enum-desc: |- + pending UnmaskingWorkflowApprovalStatusPending 待审批 + approved UnmaskingWorkflowApprovalStatusApproved 已批准 + rejected UnmaskingWorkflowApprovalStatusRejected 已驳回 + cancelled UnmaskingWorkflowApprovalStatusCancelled 已取消 + x-go-name: ApprovalStatus + created_at: + description: 申请时间 (RFC3339) + type: string + x-go-name: CreatedAt + current_assignees: + description: 当前待处理人 + items: + $ref: '#/definitions/UidWithName' + type: array + x-go-name: CurrentAssignees + datasource_name: + description: 数据源实例名称 + type: string + x-go-name: DatasourceName + datasource_uid: + description: 数据源实例ID + type: string + x-go-name: DatasourceUid + expire_time: + description: 过期时间 (RFC3339) + type: string + x-go-name: ExpireTime + source_type: + description: |- + 来源类型 + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + enum: + - data_export + - sql_workbench + type: string + x-go-enum-desc: |- + data_export UnmaskingWorkflowSourceTypeDataExport 数据导出工单 + sql_workbench UnmaskingWorkflowSourceTypeSQLWorkbench SQL工作台 + x-go-name: SourceType + source_uid: + description: 来源对象UID + type: string + x-go-name: SourceUID + usage_status: + description: |- + 使用情况 + unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看 + viewed UnmaskingWorkflowUsageStatusViewed 已查看 + enum: + - unviewed + - viewed + type: string + x-go-enum-desc: |- + unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看 + viewed UnmaskingWorkflowUsageStatusViewed 已查看 + x-go-name: UsageStatus + workflow_id: + description: 申请编号 + type: string + x-go-name: WorkflowID + type: object + x-go-package: github.com/actiontech/dms/api/dms/service/v1 UpdateBusinessTagReq: properties: business_tag: @@ -11336,106 +12070,6 @@ paths: summary: Update an existing environment tag. tags: - Project - /v1/dms/projects/{project_uid}/masking/approval-requests/{request_id}: - get: - operationId: GetPlaintextAccessRequestDetail - parameters: - - description: project uid - example: '"project_uid"' - in: path - name: project_uid - required: true - type: string - x-go-name: ProjectUid - - description: approval request id - example: 1 - format: int64 - in: path - name: request_id - required: true - type: integer - x-go-name: RequestID - responses: - "200": - description: GetPlaintextAccessRequestDetailReply - schema: - $ref: '#/definitions/GetPlaintextAccessRequestDetailReply' - default: - description: GenericResp - schema: - $ref: '#/definitions/GenericResp' - summary: 获取明文访问申请详情。 - tags: - - Masking - /v1/dms/projects/{project_uid}/masking/approval-requests/{request_id}/decisions: - post: - operationId: ProcessApprovalRequest - parameters: - - description: 项目 UID - in: path - name: project_uid - required: true - type: string - - description: 审批申请 ID - in: path - name: request_id - required: true - type: integer - - description: 处理动作信息 - in: body - name: action - required: true - schema: - $ref: '#/definitions/ProcessApprovalRequestReq' - responses: - "200": - description: 成功处理审批申请 - schema: - $ref: '#/definitions/ProcessApprovalRequestReply' - default: - description: 通用错误响应 - schema: - $ref: '#/definitions/GenericResp' - summary: 处理审批申请。 - tags: - - Masking - /v1/dms/projects/{project_uid}/masking/approval-requests/pending: - get: - operationId: ListPendingApprovalRequests - parameters: - - description: project uid - example: '"project_uid"' - in: path - name: project_uid - required: true - type: string - x-go-name: ProjectUid - - description: the maximum count of requests to be returned, default is 20 - example: "20" - format: uint32 - in: query - name: page_size - type: integer - x-go-name: PageSize - - description: the offset of requests to be returned, default is 0 - example: "0" - format: uint32 - in: query - name: page_index - type: integer - x-go-name: PageIndex - responses: - "200": - description: ListPendingApprovalRequestsReply - schema: - $ref: '#/definitions/ListPendingApprovalRequestsReply' - default: - description: GenericResp - schema: - $ref: '#/definitions/GenericResp' - summary: 查询待审批申请列表。 - tags: - - Masking /v1/dms/projects/{project_uid}/masking/overview: get: operationId: GetMaskingOverviewTree @@ -12290,6 +12924,217 @@ paths: summary: 更新脱敏模板。 tags: - Masking + /v1/dms/projects/{project_uid}/masking/unmasking-workflows: + get: + operationId: ListUnmaskingWorkflows + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + x-go-name: ProjectUid + - description: the maximum count of workflows to be returned + format: uint32 + in: query + name: page_size + required: true + type: integer + x-go-name: PageSize + - description: the offset of workflows to be returned, default is 0 + format: uint32 + in: query + name: page_index + type: integer + x-go-name: PageIndex + - description: filter the approval status + enum: + - pending + - approved + - rejected + - cancelled + in: query + name: filter_by_approval_status + type: string + x-go-enum-desc: |- + pending UnmaskingWorkflowApprovalStatusPending 待审批 + approved UnmaskingWorkflowApprovalStatusApproved 已批准 + rejected UnmaskingWorkflowApprovalStatusRejected 已驳回 + cancelled UnmaskingWorkflowApprovalStatusCancelled 已取消 + x-go-name: FilterByApprovalStatus + - description: filter the usage status + enum: + - unviewed + - viewed + in: query + name: filter_by_usage_status + type: string + x-go-enum-desc: |- + unviewed UnmaskingWorkflowUsageStatusUnviewed 未查看 + viewed UnmaskingWorkflowUsageStatusViewed 已查看 + x-go-name: FilterByUsageStatus + - description: filter db_service id + in: query + name: filter_by_db_service_uid + type: string + x-go-name: FilterByDBServiceUid + responses: + "200": + description: List unmasking workflows successfully + schema: + $ref: '#/definitions/ListUnmaskingWorkflowsReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: List unmasking workflows. + tags: + - Masking + post: + operationId: CreateUnmaskingWorkflow + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + - description: unmasking workflow info + in: body + name: unmasking_workflow + required: true + schema: + $ref: '#/definitions/CreateUnmaskingWorkflowReq' + responses: + "200": + description: Create unmasking workflow successfully + schema: + $ref: '#/definitions/CreateUnmaskingWorkflowReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: Create unmasking workflow. + tags: + - Masking + /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}: + get: + operationId: GetUnmaskingWorkflow + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + x-go-name: ProjectUid + - description: workflow id + in: path + name: workflow_id + required: true + type: string + x-go-name: WorkflowID + responses: + "200": + description: Get unmasking workflow detail successfully + schema: + $ref: '#/definitions/GetUnmaskingWorkflowReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: Get unmasking workflow detail. + tags: + - Masking + /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/approve: + post: + operationId: ApproveUnmaskingWorkflow + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + - description: workflow id + in: path + name: workflow_id + required: true + type: string + - description: approve unmasking workflow info (optional, only carries approve_reason) + in: body + name: approve_unmasking_workflow + schema: + $ref: '#/definitions/ApproveUnmaskingWorkflow' + responses: + "200": + description: Approve unmasking workflow successfully + schema: + $ref: '#/definitions/ApproveUnmaskingWorkflowReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: Approve unmasking workflow. + tags: + - Masking + /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/cancel: + post: + operationId: CancelUnmaskingWorkflow + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + x-go-name: ProjectUid + - description: workflow id + in: path + name: workflow_id + required: true + type: string + x-go-name: WorkflowID + responses: + "200": + description: Cancel unmasking workflow successfully + schema: + $ref: '#/definitions/CancelUnmaskingWorkflowReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: Cancel unmasking workflow. + tags: + - Masking + /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/reject: + post: + operationId: RejectUnmaskingWorkflow + parameters: + - description: project id + in: path + name: project_uid + required: true + type: string + - description: workflow id + in: path + name: workflow_id + required: true + type: string + - description: reject unmasking workflow info + in: body + name: reject_unmasking_workflow + required: true + schema: + $ref: '#/definitions/RejectUnmaskingWorkflow' + responses: + "200": + description: Reject unmasking workflow successfully + schema: + $ref: '#/definitions/RejectUnmaskingWorkflowReply' + default: + description: Generic error response + schema: + $ref: '#/definitions/GenericResp' + summary: Reject unmasking workflow. + tags: + - Masking /v1/dms/projects/{project_uid}/member_groups: get: operationId: ListMemberGroups diff --git a/internal/apiserver/service/data_mask_controller.go b/internal/apiserver/service/data_mask_controller.go index 560b9ab8b..9ad84b9f7 100644 --- a/internal/apiserver/service/data_mask_controller.go +++ b/internal/apiserver/service/data_mask_controller.go @@ -1,8 +1,11 @@ package service import ( + "errors" + aV1 "github.com/actiontech/dms/api/dms/service/v1" apiError "github.com/actiontech/dms/internal/apiserver/pkg/error" + pkgConst "github.com/actiontech/dms/internal/dms/pkg/constant" "github.com/actiontech/dms/pkg/dms-common/api/jwt" "github.com/labstack/echo/v4" ) @@ -28,6 +31,50 @@ func (ctl *DMSController) ListMaskingRules(c echo.Context) error { return NewOkRespWithReply(c, reply) } +// swagger:operation GET /v1/dms/masking/rules Masking ListMaskingRulesWithoutProject +// +// 查询脱敏规则列表(兼容旧入口,仅返回内置规则,可携带筛选参数)。 +// +// --- +// parameters: +// - name: source +// description: 规则来源筛选,builtin 或 custom,为空时返回全部 +// in: query +// type: string +// - name: keywords +// description: 模糊搜索关键字,匹配规则名称、描述或敏感数据类型名称 +// in: query +// type: string +// - name: page_size +// description: 分页大小,默认 20 +// in: query +// type: integer +// - name: page_index +// description: 页码(从1开始),默认 1 +// in: query +// type: integer +// responses: +// '200': +// description: 查询脱敏规则列表成功 +// schema: +// "$ref": "#/definitions/ListMaskingRulesReply" +// default: +// description: 通用错误响应 +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) ListMaskingRulesWithoutProject(c echo.Context) error { + req := &aV1.ListMaskingRulesReq{} + if err := c.Bind(req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + + reply, err := ctl.DMS.ListMaskingRules(c.Request().Context(), req) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + return NewOkRespWithReply(c, reply) +} + // swagger:route GET /v1/dms/projects/{project_uid}/masking/templates Masking ListMaskingTemplates // // 查询脱敏模板列表。 @@ -48,6 +95,35 @@ func (ctl *DMSController) ListMaskingTemplates(c echo.Context) error { return NewOkRespWithReply(c, reply) } +// swagger:route GET /v1/dms/projects/{project_uid}/db_services/{db_service_uid}/schemas/{schema_name}/tables/{table_name}/columns DBStructure ListTableColumns +// +// List table columns (internal API for lineage analysis). +// +// responses: +// 200: body:ListTableColumnsReply +// default: body:GenericResp +func (ctl *DMSController) ListTableColumns(c echo.Context) error { + // 内部接口,仅允许sys/admin用户访问 + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + if currentUserUid != pkgConst.UIDOfUserSys && currentUserUid != pkgConst.UIDOfUserAdmin { + return NewErrResp(c, errors.New("insufficient permission"), apiError.UnauthorizedErr) + } + + req := new(aV1.ListTableColumnsReq) + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + + reply, err := ctl.DMS.ListTableColumns(c.Request().Context(), req) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + return NewOkRespWithReply(c, reply) +} + // swagger:operation POST /v1/dms/projects/{project_uid}/masking/templates Masking AddMaskingTemplate // // 新增脱敏模板。 @@ -468,75 +544,393 @@ func (ctl *DMSController) GetTableColumnMaskingDetails(c echo.Context) error { return NewOkRespWithReply(c, reply) } -// swagger:route GET /v1/dms/projects/{project_uid}/masking/approval-requests/pending Masking ListPendingApprovalRequests +// swagger:operation POST /v1/dms/projects/{project_uid}/masking/unmasking-workflows Masking CreateUnmaskingWorkflow // -// 查询待审批申请列表。 +// Create unmasking workflow. // -// responses: -// 200: body:ListPendingApprovalRequestsReply -// default: body:GenericResp -func (ctl *DMSController) ListPendingApprovalRequests(c echo.Context) error { - req := &aV1.ListPendingApprovalRequestsReq{} +// --- +// parameters: +// - name: project_uid +// description: project id +// in: path +// required: true +// type: string +// - name: unmasking_workflow +// description: unmasking workflow info +// in: body +// required: true +// schema: +// "$ref": "#/definitions/CreateUnmaskingWorkflowReq" +// +// responses: +// +// '200': +// description: Create unmasking workflow successfully +// schema: +// "$ref": "#/definitions/CreateUnmaskingWorkflowReply" +// default: +// description: Generic error response +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) CreateUnmaskingWorkflow(c echo.Context) error { + req := &aV1.CreateUnmaskingWorkflowReq{} if err := bindAndValidateReq(c, req); err != nil { return NewErrResp(c, err, apiError.BadRequestErr) } - return NewOkRespWithReply(c, &aV1.ListPendingApprovalRequestsReply{}) + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + reply, err := ctl.DMS.CreateUnmaskingWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, reply) } -// swagger:route GET /v1/dms/projects/{project_uid}/masking/approval-requests/{request_id} Masking GetPlaintextAccessRequestDetail +// swagger:operation GET /v1/dms/projects/{project_uid}/masking/unmasking-workflows Masking ListUnmaskingWorkflows // -// 获取明文访问申请详情。 +// List unmasking workflows. // -// responses: -// 200: body:GetPlaintextAccessRequestDetailReply -// default: body:GenericResp -func (ctl *DMSController) GetPlaintextAccessRequestDetail(c echo.Context) error { - req := &aV1.GetPlaintextAccessRequestDetailReq{} +// --- +// parameters: +// - name: project_uid +// description: project id +// in: path +// required: true +// type: string +// - name: page_size +// description: the maximum count of workflows to be returned +// in: query +// required: true +// type: integer +// format: uint32 +// - name: page_index +// description: the offset of workflows to be returned, default is 0 +// in: query +// required: false +// type: integer +// format: uint32 +// - name: filter_by_approval_status +// description: filter the approval status +// in: query +// required: false +// type: string +// enum: [pending, approved, rejected, cancelled] +// - name: filter_by_usage_status +// description: filter the usage status +// in: query +// required: false +// type: string +// enum: [unviewed, viewed] +// - name: filter_by_db_service_uid +// description: filter db_service id +// in: query +// required: false +// type: string +// +// responses: +// +// '200': +// description: List unmasking workflows successfully +// schema: +// "$ref": "#/definitions/ListUnmaskingWorkflowsReply" +// default: +// description: Generic error response +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) ListUnmaskingWorkflows(c echo.Context) error { + req := &aV1.ListUnmaskingWorkflowsReq{} if err := bindAndValidateReq(c, req); err != nil { return NewErrResp(c, err, apiError.BadRequestErr) } - return NewOkRespWithReply(c, &aV1.GetPlaintextAccessRequestDetailReply{}) + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + reply, err := ctl.DMS.ListUnmaskingWorkflows(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, reply) } -// swagger:operation POST /v1/dms/projects/{project_uid}/masking/approval-requests/{request_id}/decisions Masking ProcessApprovalRequest +// swagger:operation GET /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id} Masking GetUnmaskingWorkflow // -// 处理审批申请。 +// Get unmasking workflow detail. // // --- // parameters: // - name: project_uid -// description: 项目 UID +// description: project id // in: path // required: true // type: string -// - name: request_id -// description: 审批申请 ID +// - name: workflow_id +// description: workflow id // in: path // required: true -// type: integer -// - name: action -// description: 处理动作信息 +// type: string +// +// responses: +// +// '200': +// description: Get unmasking workflow detail successfully +// schema: +// "$ref": "#/definitions/GetUnmaskingWorkflowReply" +// default: +// description: Generic error response +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) GetUnmaskingWorkflow(c echo.Context) error { + req := &aV1.GetUnmaskingWorkflowReq{} + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + reply, err := ctl.DMS.GetUnmaskingWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, reply) +} + +// swagger:operation POST /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/approve Masking ApproveUnmaskingWorkflow +// +// Approve unmasking workflow. +// +// --- +// parameters: +// - name: project_uid +// description: project id +// in: path +// required: true +// type: string +// - name: workflow_id +// description: workflow id +// in: path +// required: true +// type: string +// - name: approve_unmasking_workflow +// description: approve unmasking workflow info (optional, only carries approve_reason) +// in: body +// required: false +// schema: +// "$ref": "#/definitions/ApproveUnmaskingWorkflow" +// +// responses: +// +// '200': +// description: Approve unmasking workflow successfully +// schema: +// "$ref": "#/definitions/ApproveUnmaskingWorkflowReply" +// default: +// description: Generic error response +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) ApproveUnmaskingWorkflow(c echo.Context) error { + req := &aV1.ApproveUnmaskingWorkflowReq{} + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + err = ctl.DMS.ApproveUnmaskingWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, &aV1.ApproveUnmaskingWorkflowReply{}) +} + +// swagger:operation POST /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/reject Masking RejectUnmaskingWorkflow +// +// Reject unmasking workflow. +// +// --- +// parameters: +// - name: project_uid +// description: project id +// in: path +// required: true +// type: string +// - name: workflow_id +// description: workflow id +// in: path +// required: true +// type: string +// - name: reject_unmasking_workflow +// description: reject unmasking workflow info // in: body // required: true // schema: -// "$ref": "#/definitions/ProcessApprovalRequestReq" +// "$ref": "#/definitions/RejectUnmaskingWorkflow" // // responses: // // '200': -// description: 成功处理审批申请 +// description: Reject unmasking workflow successfully // schema: -// "$ref": "#/definitions/ProcessApprovalRequestReply" +// "$ref": "#/definitions/RejectUnmaskingWorkflowReply" // default: -// description: 通用错误响应 +// description: Generic error response +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) RejectUnmaskingWorkflow(c echo.Context) error { + req := &aV1.RejectUnmaskingWorkflowReq{} + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + err = ctl.DMS.RejectUnmaskingWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, &aV1.RejectUnmaskingWorkflowReply{}) +} + +// swagger:operation POST /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/cancel Masking CancelUnmaskingWorkflow +// +// Cancel unmasking workflow. +// +// --- +// parameters: +// - name: project_uid +// description: project id +// in: path +// required: true +// type: string +// - name: workflow_id +// description: workflow id +// in: path +// required: true +// type: string +// +// responses: +// +// '200': +// description: Cancel unmasking workflow successfully +// schema: +// "$ref": "#/definitions/CancelUnmaskingWorkflowReply" +// default: +// description: Generic error response // schema: // "$ref": "#/definitions/GenericResp" -func (ctl *DMSController) ProcessApprovalRequest(c echo.Context) error { - req := &aV1.ProcessApprovalRequestReq{} +func (ctl *DMSController) CancelUnmaskingWorkflow(c echo.Context) error { + req := &aV1.CancelUnmaskingWorkflowReq{} if err := bindAndValidateReq(c, req); err != nil { return NewErrResp(c, err, apiError.BadRequestErr) } - return NewOkRespWithReply(c, &aV1.ProcessApprovalRequestReply{}) + + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + + err = ctl.DMS.CancelUnmaskingWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + + return NewOkRespWithReply(c, &aV1.CancelUnmaskingWorkflowReply{}) +} + +// swagger:operation POST /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/activate Masking ActivateUnmaskingWorkflowView +// +// Activate plaintext view window for applicant. +// +// --- +// parameters: +// - name: project_uid +// in: path +// required: true +// type: string +// - name: workflow_id +// in: path +// required: true +// type: string +// +// responses: +// '200': +// schema: +// "$ref": "#/definitions/ActivateUnmaskingWorkflowViewReply" +// default: +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) ActivateUnmaskingWorkflowView(c echo.Context) error { + req := &aV1.ActivateUnmaskingWorkflowViewReq{} + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + reply, err := ctl.DMS.ActivateUnmaskingWorkflowView(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + return NewOkRespWithReply(c, reply) +} + +// swagger:operation GET /v1/dms/projects/{project_uid}/masking/unmasking-workflows/{workflow_id}/plaintext Masking GetUnmaskingWorkflowPlaintext +// +// Get plaintext query snapshot during active view window. +// +// --- +// parameters: +// - name: project_uid +// in: path +// required: true +// type: string +// - name: workflow_id +// in: path +// required: true +// type: string +// +// responses: +// '200': +// schema: +// "$ref": "#/definitions/GetUnmaskingWorkflowPlaintextReply" +// default: +// schema: +// "$ref": "#/definitions/GenericResp" +func (ctl *DMSController) GetUnmaskingWorkflowPlaintext(c echo.Context) error { + req := &aV1.GetUnmaskingWorkflowPlaintextReq{} + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.UnauthorizedErr) + } + reply, err := ctl.DMS.GetUnmaskingWorkflowPlaintext(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + return NewOkRespWithReply(c, reply) } // swagger:route GET /v1/dms/projects/{project_uid}/masking/rules/{rule_id} Masking GetMaskingRuleDetail diff --git a/internal/apiserver/service/dms_controller.go b/internal/apiserver/service/dms_controller.go index 5582b58ed..d0d775772 100644 --- a/internal/apiserver/service/dms_controller.go +++ b/internal/apiserver/service/dms_controller.go @@ -3976,6 +3976,32 @@ func (ctl *DMSController) ExportDataExportWorkflow(c echo.Context) error { return NewOkResp(c) } +// swagger:route GET /v1/dms/projects/{project_uid}/data_export_workflows/{data_export_workflow_uid}/original-export/download DataExportWorkflows DownloadOriginalDataExportWorkflow +// +// Download unmasked SQL query results as a zip file. Each request runs export in memory; files are not persisted. +// +// responses: +// 200: DownloadOriginalDataExportWorkflowReply +// default: body:GenericResp +func (ctl *DMSController) DownloadOriginalDataExportWorkflow(c echo.Context) error { + req := new(aV1.DownloadOriginalDataExportWorkflowReq) + if err := bindAndValidateReq(c, req); err != nil { + return NewErrResp(c, err, apiError.BadRequestErr) + } + currentUserUid, err := jwt.GetUserUidStrFromContext(c) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + fileName, content, err := ctl.DMS.DownloadOriginalDataExportWorkflow(c.Request().Context(), req, currentUserUid) + if err != nil { + return NewErrResp(c, err, apiError.DMSServiceErr) + } + c.Response().Header().Set(echo.HeaderContentDisposition, + mime.FormatMediaType("attachment", map[string]string{"filename": fileName})) + + return c.Blob(http.StatusOK, "application/zip", content) +} + // swagger:operation POST /v1/dms/projects/{project_uid}/data_export_tasks DataExportTask AddDataExportTask // // Add data_export task. diff --git a/internal/apiserver/service/router.go b/internal/apiserver/service/router.go index 825f1d880..a2e5cdba1 100644 --- a/internal/apiserver/service/router.go +++ b/internal/apiserver/service/router.go @@ -7,6 +7,8 @@ import ( dmsMiddleware "github.com/actiontech/dms/internal/apiserver/middleware" "github.com/actiontech/dms/internal/dms/biz" + dmsService "github.com/actiontech/dms/internal/dms/service" + "github.com/actiontech/dms/internal/dms/storage" "github.com/actiontech/dms/internal/pkg/locale" "github.com/actiontech/dms/internal/pkg/utils" sqlWorkbenchService "github.com/actiontech/dms/internal/sql_workbench/service" @@ -223,6 +225,7 @@ func (s *APIServer) initRouter() error { dataExportWorkflowsV1.POST("/:data_export_workflow_uid/approve", s.DMSController.ApproveDataExportWorkflow) dataExportWorkflowsV1.POST("/:data_export_workflow_uid/reject", s.DMSController.RejectDataExportWorkflow) dataExportWorkflowsV1.POST("/:data_export_workflow_uid/export", s.DMSController.ExportDataExportWorkflow) + dataExportWorkflowsV1.GET("/:data_export_workflow_uid/original-export/download", s.DMSController.DownloadOriginalDataExportWorkflow) dataExportWorkflowsV1.POST("/cancel", s.DMSController.CancelDataExportWorkflow) // 内部接口,仅允许sys用户访问 @@ -283,6 +286,21 @@ func (s *APIServer) initRouter() error { sqlWorkbenchV1.Use(s.SqlWorkbenchController.SqlWorkbenchService.Login()) + // 添加数据脱敏中间件 + sqlWorkbenchV1.Use(sqlWorkbenchService.GetDataMaskingMiddleware(sqlWorkbenchService.DataMaskingMiddlewareConfig{ + SqlResultMasker: s.SqlWorkbenchController.SqlWorkbenchService.GetSqlResultMasker(), + DBServiceUsecase: s.DMSController.DMS.DBServiceUsecase, + SqlWorkbenchService: s.SqlWorkbenchController.SqlWorkbenchService, + UnmaskingWorkflowUsecase: s.DMSController.DMS.UnmaskingWorkflowUsecase, + })) + + // 添加查看原文 SQL 替换中间件 + sqlWorkbenchV1.Use(sqlWorkbenchService.GetUnmaskingWorkflowMiddleware(sqlWorkbenchService.DataMaskingMiddlewareConfig{ + DBServiceUsecase: s.DMSController.DMS.DBServiceUsecase, + SqlWorkbenchService: s.SqlWorkbenchController.SqlWorkbenchService, + UnmaskingWorkflowUsecase: s.DMSController.DMS.UnmaskingWorkflowUsecase, + })) + // 添加操作日志记录中间件 sqlWorkbenchV1.Use(sqlWorkbenchService.GetOperationLogBodyDumpMiddleware(sqlWorkbenchService.OperationLogMiddlewareConfig{ CbOperationLogUsecase: s.DMSController.DMS.CbOperationLogUsecase, @@ -485,6 +503,25 @@ func (s *APIServer) installController() error { s.DMSController = DMSController s.SqlWorkbenchController = sqlWorkbenchController + // 初始化 SQL Workbench 脱敏组件,与 DMS 共用同一套存储配置 + st, err := storage.NewStorage(s.logger, &storage.StorageConfig{ + User: s.opts.ServiceOpts.Database.UserName, + Password: s.opts.ServiceOpts.Database.Password, + Host: s.opts.ServiceOpts.Database.Host, + Port: s.opts.ServiceOpts.Database.Port, + Schema: s.opts.ServiceOpts.Database.Database, + Debug: s.opts.ServiceOpts.Database.Debug, + AutoMigrate: s.opts.ServiceOpts.Database.AutoMigrate, + }) + if err != nil { + return fmt.Errorf("failed to initialize storage for masker: %v", err) + } + masker, err := dmsService.NewSQLWorkbenchSQLResultMasker(s.logger, st) + if err != nil { + return fmt.Errorf("failed to create sql result masker: %v", err) + } + s.SqlWorkbenchController.SqlWorkbenchService.SetSqlResultMasker(masker) + // s.AuthController.RegisterPlugin(s.DMSController.GetRegisterPluginFn()) return nil } diff --git a/internal/apiserver/service/service.go b/internal/apiserver/service/service.go index b507b425c..cdcc4d53f 100644 --- a/internal/apiserver/service/service.go +++ b/internal/apiserver/service/service.go @@ -39,7 +39,7 @@ import ( ) type APIServer struct { - DMSController *DMSController + DMSController *DMSController SqlWorkbenchController *SqlWorkbenchController // more controllers @@ -58,14 +58,18 @@ func NewAPIServer(logger utilLog.Logger, opts *conf.DMSOptions) (*APIServer, err } message := err.Error() + statusCode := http.StatusBadRequest + respCode := int(apiError.BadRequestErr) if httpErr, ok := err.(*echo.HTTPError); ok { + statusCode = httpErr.Code + respCode = httpErr.Code if msg, ok := httpErr.Message.(string); ok && msg != "" { message = msg } } - _ = c.JSON(http.StatusBadRequest, bV1.GenericResp{ - Code: int(apiError.BadRequestErr), + _ = c.JSON(statusCode, bV1.GenericResp{ + Code: respCode, Message: message, }) } diff --git a/internal/data_masking/biz/unmasking_workflow.go b/internal/data_masking/biz/unmasking_workflow.go new file mode 100644 index 000000000..3a8f20237 --- /dev/null +++ b/internal/data_masking/biz/unmasking_workflow.go @@ -0,0 +1,288 @@ +package biz + +import ( + "context" + "time" + + maskingCore "github.com/actiontech/dms/internal/data_masking/core" +) + +// MaskingConfigStatus 脱敏配置状态 +// swagger:enum MaskingConfigStatus +type MaskingConfigStatus string + +const ( + MaskingConfigStatusPendingConfirm MaskingConfigStatus = "PENDING_CONFIRM" // 系统发现,待人工确认 + MaskingConfigStatusConfigured MaskingConfigStatus = "CONFIGURED" // 用户已确认/手动配置 + MaskingConfigStatusSystemConfirmed MaskingConfigStatus = "SYSTEM_CONFIRMED" // 系统已确认 +) + +// ColumnMaskingConfig 列级别脱敏配置领域模型 +// swagger:model ColumnMaskingConfig +type ColumnMaskingConfig struct { + // 配置记录 ID + ID uint `json:"id"` + // 数据源 UID + DBServiceUID string `json:"db_service_uid"` + // 列 ID(db_columns.id) + ColumnID uint `json:"column_id"` + // Schema 名称 + SchemaName string `json:"schema_name"` + // 表名 + TableName string `json:"table_name"` + // 列名 + ColumnName string `json:"column_name"` + // 是否启用脱敏 + IsMaskingEnabled bool `json:"is_masking_enabled"` + // 脱敏规则 ID + MaskingRuleID int `json:"masking_rule_id"` + // 脱敏规则名称(中文) + MaskingRuleName string `json:"masking_rule_name"` + // 置信度 + Confidence maskingCore.Confidence `json:"confidence,omitempty"` + // 配置状态 + Status MaskingConfigStatus `json:"status"` + // 创建时间 + CreatedAt time.Time `json:"created_at"` + // 更新时间 + UpdatedAt time.Time `json:"updated_at"` +} + +// swagger:model TableRef +type TableRef struct { + // Schema 名称 + Schema string `json:"schema"` + // 表名 + Table string `json:"table"` + // 表别名 + Alias string `json:"alias"` +} + +// swagger:model ColumnRef +type ColumnRef struct { + // Schema 名称 + Schema string `json:"schema"` + // 表名 + Table string `json:"table"` + // 列名 + Column string `json:"column"` +} + +// swagger:model ResultColumn +type ResultColumn struct { + // 结果列名 + Name string `json:"name"` + // 结果列表达式(SQL 片段) + Expression string `json:"expression"` + // 来源列列表 + Sources []ColumnRef `json:"sources"` +} + +// swagger:model LineageNode +type LineageNode struct { + // 节点 ID(图内唯一) + ID string `json:"id"` + // 节点类型 + Type NodeType `json:"type"` + // 节点展示名 + Name string `json:"name"` + // Schema 名称(列节点可能存在) + Schema string `json:"schema"` + // 表名(列节点可能存在) + Table string `json:"table"` + // 列名(列节点可能存在) + Column string `json:"column"` + // 表达式内容(表达式节点可能存在) + Expr string `json:"expr"` +} + +// swagger:model LineageEdge +type LineageEdge struct { + // 起点节点 ID + FromID string `json:"from_id"` + // 终点节点 ID + ToID string `json:"to_id"` + // 边类型 + Type EdgeType `json:"type"` +} + +// swagger:model AnalyzeResult +type AnalyzeResult struct { + // 分析标题/摘要 + Title string `json:"title"` + // 原始 SQL + OriginalSQL string `json:"original_sql"` + // 解析到的表引用列表 + Tables []TableRef `json:"tables"` + // 解析到的源列列表 + SourceColumns []ColumnRef `json:"source_columns"` + // 解析到的结果列列表 + ResultColumns []ResultColumn `json:"result_columns"` + // 血缘图节点 + Nodes []LineageNode `json:"nodes"` + // 血缘图边 + Edges []LineageEdge `json:"edges"` + // 警告信息(解析不完整等) + Warnings []string `json:"warnings,omitempty"` +} + +// NodeType 血缘节点类型 +// swagger:enum NodeType +type NodeType string + +const ( + // 源列节点 + NodeTypeSource NodeType = "source_column" + // 表达式节点 + NodeTypeExpression NodeType = "expression" + // 结果列节点 + NodeTypeResult NodeType = "result_column" + // 表节点 + NodeTypeTable NodeType = "table" +) + +// EdgeType 血缘边类型 +// swagger:enum EdgeType +type EdgeType string + +const ( + // 直接依赖 + EdgeTypeDirect EdgeType = "direct" + // 转换/计算依赖 + EdgeTypeTransform EdgeType = "transform" + // 聚合依赖 + EdgeTypeAggregate EdgeType = "aggregate" +) + +// UnmaskingWorkflowApprovalStatus 审批状态 +// swagger:enum UnmaskingWorkflowApprovalStatus +type UnmaskingWorkflowApprovalStatus string + +const ( + // 待审批 + UnmaskingWorkflowApprovalStatusPending UnmaskingWorkflowApprovalStatus = "pending" + // 已批准 + UnmaskingWorkflowApprovalStatusApproved UnmaskingWorkflowApprovalStatus = "approved" + // 已驳回 + UnmaskingWorkflowApprovalStatusRejected UnmaskingWorkflowApprovalStatus = "rejected" + // 已取消 + UnmaskingWorkflowApprovalStatusCancelled UnmaskingWorkflowApprovalStatus = "cancelled" +) + +func (s UnmaskingWorkflowApprovalStatus) String() string { + return string(s) +} + +// UnmaskingWorkflowUsageStatus 使用情况 +// swagger:enum UnmaskingWorkflowUsageStatus +type UnmaskingWorkflowUsageStatus string + +const ( + // 未查看 + UnmaskingWorkflowUsageStatusUnviewed UnmaskingWorkflowUsageStatus = "unviewed" + // 已查看 + UnmaskingWorkflowUsageStatusViewed UnmaskingWorkflowUsageStatus = "viewed" +) + +func (s UnmaskingWorkflowUsageStatus) String() string { + return string(s) +} + +// UnmaskingWorkflowSourceType 来源类型 +// swagger:enum UnmaskingWorkflowSourceType +type UnmaskingWorkflowSourceType string + +const ( + // 数据导出工单 + UnmaskingWorkflowSourceTypeDataExport UnmaskingWorkflowSourceType = "data_export" + // SQL工作台 + UnmaskingWorkflowSourceTypeSQLWorkbench UnmaskingWorkflowSourceType = "sql_workbench" +) + +func (s UnmaskingWorkflowSourceType) String() string { + return string(s) +} + +// UnmaskingOriginalDataCredentialArgs 工单凭证路径参数,供 CheckOriginalDataAccess 使用;nil 表示仅做权限直通(不入库解析工单)。enterprise 与 dms 构建共用。 +type UnmaskingOriginalDataCredentialArgs struct { + SourceType UnmaskingWorkflowSourceType + SourceUID string + Credential string +} + +// UnmaskingAction 操作动作类型 +// swagger:enum UnmaskingAction +type UnmaskingAction string + +const ( + // 提交申请 + UnmaskingActionSubmit UnmaskingAction = "submit" + // 批准申请 + UnmaskingActionApprove UnmaskingAction = "approve" + // 驳回申请 + UnmaskingActionReject UnmaskingAction = "reject" + // 查看工单详情 + UnmaskingActionViewOriginalDataWorkflowDetail UnmaskingAction = "view_unmasking_workflow_detail" + // 查看原文 + UnmaskingActionViewOriginalData UnmaskingAction = "view_full_original_data" + // 下载原文 + UnmaskingActionDownloadOriginalData UnmaskingAction = "download_full_original_data" + // 取消申请 + UnmaskingActionCancel UnmaskingAction = "cancel" + // 激活查看原文(工单详情) + UnmaskingActionActivateView UnmaskingAction = "activate_view" +) + +func (a UnmaskingAction) String() string { + return string(a) +} + +// UnmaskingWorkflowViewState 申请人明文查看状态(详情页展示) +// swagger:enum UnmaskingWorkflowViewState +type UnmaskingWorkflowViewState string + +const ( + UnmaskingWorkflowViewStateNotActivated UnmaskingWorkflowViewState = "not_activated" + UnmaskingWorkflowViewStateActive UnmaskingWorkflowViewState = "active" + UnmaskingWorkflowViewStateViewExpired UnmaskingWorkflowViewState = "view_expired" + UnmaskingWorkflowViewStateActivationExpired UnmaskingWorkflowViewState = "activation_expired" +) + +func (s UnmaskingWorkflowViewState) String() string { + return string(s) +} + +// 与 DMS OpRangeType 字符串取值一致,用于 UnmaskingOpPermissionRange 判定。 +const ( + UnmaskingOpRangeProject = "project" + UnmaskingOpRangeDBService = "db_service" +) + +// UnmaskingOpPermissionRange 查看原文工单权限判定用的「操作权限 + 范围」快照。 +type UnmaskingOpPermissionRange struct { + OpPermissionUID string + OpRangeType string + RangeUIDs []string +} + +// UnmaskingWorkflowOpPermissionVerifier 操作权限校验能力。 +type UnmaskingWorkflowOpPermissionVerifier interface { + IsUserDMSAdmin(ctx context.Context, userUID string) (bool, error) + CanOpGlobal(ctx context.Context, userUID string, isBusinessWrite bool) (bool, error) + CanViewGlobal(ctx context.Context, userUID string) (bool, error) + IsUserProjectAdmin(ctx context.Context, userUID, projectUID string, isBusinessWrite bool) (bool, error) + GetUserOpPermissionInProject(ctx context.Context, userUID, projectUID string) ([]UnmaskingOpPermissionRange, error) + GetCanOpDBUsers(ctx context.Context, projectUID, dbServiceUID string, needOpPermissionTypes []string, isBusinessWrite bool) ([]string, error) +} + +// UnmaskingWorkflowUserDirectory 用户领域:工单列表/详情等场景将用户 UID 解析为展示名。 +// 由 DMS service 层适配 UserUsecase 实现。 +type UnmaskingWorkflowUserDirectory interface { + GetUserNamesByUIDs(ctx context.Context, uids []string) (map[string]string, error) +} + +// UnmaskingWorkflowDBServiceDirectory 数据源领域:将 DBService UID 解析为实例名称。 +// 由 DMS service 层适配 DBServiceUsecase 实现。 +type UnmaskingWorkflowDBServiceDirectory interface { + GetDBServiceNamesByUIDs(ctx context.Context, uids []string) (map[string]string, error) +} diff --git a/internal/data_masking/biz/unmasking_workflow_ce.go b/internal/data_masking/biz/unmasking_workflow_ce.go new file mode 100644 index 000000000..2ccb3e44d --- /dev/null +++ b/internal/data_masking/biz/unmasking_workflow_ce.go @@ -0,0 +1,84 @@ +//go:build !dms + +package biz + +import ( + "context" +) + +type UnmaskingWorkflowUsecase struct { +} + +// UnmaskingSQL 非 dms 构建占位;与 enterprise 中间件读取的字段对齐。 +type UnmaskingSQL struct { + UID string + SQLContent string +} + +// UnmaskingWorkflow 非 dms 构建下的占位类型;enterprise 会读 UID / ProjectUID 等(如 data_export_workflow_ee)。 +type UnmaskingWorkflow struct { + UID string + ProjectUID string + ApplicantUID string + UnmaskingSQLs []*UnmaskingSQL +} + +type CreateUnmaskingWorkflowArgs struct{} + +type UnmaskingDBConfig struct{} + +type ListUnmaskingWorkflowsOption struct{} + +func NewUnmaskingWorkflowUsecase(ctx context.Context) *UnmaskingWorkflowUsecase { + return &UnmaskingWorkflowUsecase{} +} + +func (u *UnmaskingWorkflowUsecase) CreateUnmaskingWorkflow(ctx context.Context, args *CreateUnmaskingWorkflowArgs) (string, error) { + return "", nil +} + +func (u *UnmaskingWorkflowUsecase) GetUnmaskingWorkflow(ctx context.Context, workflowUID string) (*UnmaskingWorkflow, error) { + return nil, nil +} + +func (u *UnmaskingWorkflowUsecase) ListUnmaskingWorkflows(ctx context.Context, opt *ListUnmaskingWorkflowsOption) ([]*UnmaskingWorkflow, error) { + return nil, nil +} + +func (u *UnmaskingWorkflowUsecase) ApproveUnmaskingWorkflow(ctx context.Context, workflowUID string) error { + return nil +} + +func (u *UnmaskingWorkflowUsecase) RejectUnmaskingWorkflow(ctx context.Context, workflowUID string) error { + return nil +} + +func (u *UnmaskingWorkflowUsecase) CancelUnmaskingWorkflow(ctx context.Context, workflowUID string) error { + return nil +} + +func (u *UnmaskingWorkflowUsecase) GetUnmaskingWorkflowAssignees(ctx context.Context, projectUID, datasourceUID, applicantUID string) ([]string, error) { + return nil, nil +} + +func (u *UnmaskingWorkflowUsecase) CheckOriginalDataAccess(ctx context.Context, projectUID, datasourceUID, userUID string, credential *UnmaskingOriginalDataCredentialArgs) (bool, *UnmaskingWorkflow, error) { + return false, nil, nil +} + +// MarkWorkflowUsage 完整实现仅在 dms 构建中提供;非 dms 占位。 +func (u *UnmaskingWorkflowUsecase) MarkWorkflowUsage(_ context.Context, _, _, _ string, _ UnmaskingAction) error { + return nil +} + +// GetUnmaskingWorkflowDetail 完整实现仅在 dms 构建中提供;非 dms 占位(运行时 enterprise 且未启用 dms 时 usecase 一般为 nil)。 +func (u *UnmaskingWorkflowUsecase) GetUnmaskingWorkflowDetail(_ context.Context, _, _, _ string) (*UnmaskingWorkflow, error) { + return &UnmaskingWorkflow{}, nil +} + +// AnalyzeLineageAndBuildMaskingSnapshot 完整实现仅在 dms 构建中提供;非 dms 构建占位以满足跨标签编译。 +func (u *UnmaskingWorkflowUsecase) AnalyzeLineageAndBuildMaskingSnapshot( + _ context.Context, + _, _, _, _ string, +) (*AnalyzeResult, []*ColumnMaskingConfig) { + return nil, nil +} diff --git a/internal/data_masking/core/types.go b/internal/data_masking/core/types.go new file mode 100644 index 000000000..8df3416dd --- /dev/null +++ b/internal/data_masking/core/types.go @@ -0,0 +1,14 @@ +package core + +// Confidence 置信度 (例如: High, Medium, Low) +// swagger:enum Confidence +type Confidence string + +const ( + // 高:高度确信为敏感数据 + ConfidenceHigh Confidence = "High" + // 中:中等确信为敏感数据 + ConfidenceMedium Confidence = "Medium" + // 低:低确信为敏感数据 + ConfidenceLow Confidence = "Low" +) diff --git a/internal/dms/biz/cloudbeaver.go b/internal/dms/biz/cloudbeaver.go index b0b1e8325..4488fb836 100644 --- a/internal/dms/biz/cloudbeaver.go +++ b/internal/dms/biz/cloudbeaver.go @@ -65,7 +65,7 @@ func (c CloudbeaverConnection) PrimaryKey() string { } type SQLResultMasker interface { - MaskSQLResults(ctx context.Context, result *model.SQLExecuteInfo, dbServiceUID, schemaName string) error + MaskSQLResults(ctx context.Context, result *model.SQLExecuteInfo, dbServiceUID, schemaName, projectUID string) error } type CloudbeaverRepo interface { @@ -91,13 +91,12 @@ type CloudbeaverUsecase struct { sqlResultMasker SQLResultMasker cbOperationLogUsecase *CbOperationLogUsecase projectUsecase *ProjectUsecase - maskingTaskRepo MaskingTaskRepo repo CloudbeaverRepo proxyTargetRepo ProxyTargetRepo maintenanceTimeUsecase *MaintenanceTimeUsecase } -func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, sqlResultMasker SQLResultMasker, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseCase *CbOperationLogUsecase, projectUsecase *ProjectUsecase, maskingTaskRepo MaskingTaskRepo, maintenanceTimeUsecase *MaintenanceTimeUsecase) (cu *CloudbeaverUsecase) { +func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase *UserUsecase, dbServiceUsecase *DBServiceUsecase, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, dmsConfigUseCase *DMSConfigUseCase, sqlResultMasker SQLResultMasker, cloudbeaverRepo CloudbeaverRepo, proxyTargetRepo ProxyTargetRepo, cbOperationUseCase *CbOperationLogUsecase, projectUsecase *ProjectUsecase, maintenanceTimeUsecase *MaintenanceTimeUsecase) (cu *CloudbeaverUsecase) { cu = &CloudbeaverUsecase{ repo: cloudbeaverRepo, proxyTargetRepo: proxyTargetRepo, @@ -108,7 +107,6 @@ func NewCloudbeaverUsecase(log utilLog.Logger, cfg *CloudbeaverCfg, userUsecase sqlResultMasker: sqlResultMasker, cbOperationLogUsecase: cbOperationUseCase, projectUsecase: projectUsecase, - maskingTaskRepo: maskingTaskRepo, cloudbeaverCfg: cfg, log: utilLog.NewHelper(log, utilLog.WithMessageKey("biz.cloudbeaver")), maintenanceTimeUsecase: maintenanceTimeUsecase, @@ -339,6 +337,7 @@ type taskMaskingContext struct { Enabled bool DBServiceUID string SchemaName string + ProjectUID string } var ( @@ -466,11 +465,12 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc { return err } - isMaskingEnabled, _ := cu.maskingTaskRepo.CheckMaskingTaskExist(c.Request().Context(), dbService.UID) + isMaskingEnabled := cu.dbServiceUsecase != nil && cu.dbServiceUsecase.HasSensitiveDataMaskingTask(c.Request().Context(), dbService.UID) // 构建任务ID与数据脱敏的关联 return cu.buildTaskIdAssocDataMasking(cloudbeaverResBuf.Bytes(), taskMaskingContext{ Enabled: isMaskingEnabled, DBServiceUID: dbService.UID, + ProjectUID: dbService.ProjectUID, }) } @@ -495,10 +495,11 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc { cu.log.Error(err) } - isMaskingEnabled, _ := cu.maskingTaskRepo.CheckMaskingTaskExist(c.Request().Context(), dbService.UID) + isMaskingEnabled := cu.dbServiceUsecase != nil && cu.dbServiceUsecase.HasSensitiveDataMaskingTask(c.Request().Context(), dbService.UID) maskCtx := taskMaskingContext{ Enabled: isMaskingEnabled, DBServiceUID: dbService.UID, + ProjectUID: dbService.ProjectUID, } if ep, epErr := cu.getWorkflowExecParams(c, params); epErr == nil { maskCtx.SchemaName = ep.instanceSchema @@ -564,11 +565,12 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc { return err } - isMaskingEnabled, _ := cu.maskingTaskRepo.CheckMaskingTaskExist(c.Request().Context(), dbService.UID) + isMaskingEnabled := cu.dbServiceUsecase != nil && cu.dbServiceUsecase.HasSensitiveDataMaskingTask(c.Request().Context(), dbService.UID) // 构建任务ID与数据脱敏的关联 return cu.buildTaskIdAssocDataMasking(cloudbeaverResBuf.Bytes(), taskMaskingContext{ Enabled: isMaskingEnabled, DBServiceUID: dbService.UID, + ProjectUID: dbService.ProjectUID, }) } @@ -698,11 +700,12 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc { } if params.OperationName == "asyncSqlExecuteQuery" { - isMaskingEnabled, _ := cu.maskingTaskRepo.CheckMaskingTaskExist(c.Request().Context(), dbService.UID) + isMaskingEnabled := cu.dbServiceUsecase != nil && cu.dbServiceUsecase.HasSensitiveDataMaskingTask(c.Request().Context(), dbService.UID) if err := cu.buildTaskIdAssocDataMasking(cloudbeaverResBuf.Bytes(), taskMaskingContext{ Enabled: isMaskingEnabled, DBServiceUID: dbService.UID, SchemaName: maskingSchemaName, + ProjectUID: dbService.ProjectUID, }); err != nil { return nil, err } @@ -754,7 +757,7 @@ func (cu *CloudbeaverUsecase) GraphQLDistributor() echo.MiddlewareFunc { if cu.sqlResultMasker == nil { return nil } - return cu.sqlResultMasker.MaskSQLResults(ctx, result, maskingCtx.DBServiceUID, maskingCtx.SchemaName) + return cu.sqlResultMasker.MaskSQLResults(ctx, result, maskingCtx.DBServiceUID, maskingCtx.SchemaName, maskingCtx.ProjectUID) } // 创建GraphQL可执行schema diff --git a/internal/dms/biz/data_export_workflow.go b/internal/dms/biz/data_export_workflow.go index 148acaf2c..96b19eebe 100644 --- a/internal/dms/biz/data_export_workflow.go +++ b/internal/dms/biz/data_export_workflow.go @@ -8,6 +8,8 @@ import ( pkgConst "github.com/actiontech/dms/internal/dms/pkg/constant" dmsCommonV1 "github.com/actiontech/dms/pkg/dms-common/api/dms/v1" utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log" + + dataMaskingBiz "github.com/actiontech/dms/internal/data_masking/biz" ) var ErrDataExportWorkflowNameDuplicate = errors.New("data export workflow name duplicate") @@ -28,6 +30,10 @@ func (dews DataExportWorkflowStatus) String() string { return string(dews) } +func isDataExportWorkflowStatusAllowDownloadOriginal(status DataExportWorkflowStatus) bool { + return status == DataExportWorkflowStatusFinish +} + type EventType string const ( @@ -134,12 +140,13 @@ type DataExportWorkflowUsecase struct { webhookUsecase *WebHookConfigurationUsecase userUsecase *UserUsecase systemVariableUsecase *SystemVariableUsecase - maskingTaskRepo MaskingTaskRepo + dbServiceUsecase *DBServiceUsecase + unmaskingWorkflowUsecase *dataMaskingBiz.UnmaskingWorkflowUsecase log *utilLog.Helper reportHost string } -func NewDataExportWorkflowUsecase(logger utilLog.Logger, tx TransactionGenerator, repo WorkflowRepo, dataExportTaskRepo DataExportTaskRepo, dbServiceRepo DBServiceRepo, maskingConfigRepo DataExportMaskingConfigRepo, maskingRuleRepo DataExportMaskingRuleRepo, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, projectUsecase *ProjectUsecase, proxyTargetRepo ProxyTargetRepo, clusterUseCase *ClusterUsecase, webhookUsecase *WebHookConfigurationUsecase, userUsecase *UserUsecase, systemVariableUsecase *SystemVariableUsecase, maskingTaskRepo MaskingTaskRepo, reportHost string) *DataExportWorkflowUsecase { +func NewDataExportWorkflowUsecase(logger utilLog.Logger, tx TransactionGenerator, repo WorkflowRepo, dataExportTaskRepo DataExportTaskRepo, dbServiceRepo DBServiceRepo, maskingConfigRepo DataExportMaskingConfigRepo, maskingRuleRepo DataExportMaskingRuleRepo, opPermissionVerifyUsecase *OpPermissionVerifyUsecase, projectUsecase *ProjectUsecase, proxyTargetRepo ProxyTargetRepo, clusterUseCase *ClusterUsecase, webhookUsecase *WebHookConfigurationUsecase, userUsecase *UserUsecase, systemVariableUsecase *SystemVariableUsecase, dbServiceUsecase *DBServiceUsecase, unmaskingWorkflowUsecase *dataMaskingBiz.UnmaskingWorkflowUsecase, reportHost string) *DataExportWorkflowUsecase { return &DataExportWorkflowUsecase{ tx: tx, repo: repo, @@ -154,7 +161,8 @@ func NewDataExportWorkflowUsecase(logger utilLog.Logger, tx TransactionGenerator webhookUsecase: webhookUsecase, userUsecase: userUsecase, systemVariableUsecase: systemVariableUsecase, - maskingTaskRepo: maskingTaskRepo, + dbServiceUsecase: dbServiceUsecase, + unmaskingWorkflowUsecase: unmaskingWorkflowUsecase, log: utilLog.NewHelper(logger, utilLog.WithMessageKey("biz.dataExportWorkflow")), reportHost: reportHost, } diff --git a/internal/dms/biz/data_export_workflow_ce.go b/internal/dms/biz/data_export_workflow_ce.go index 181b39540..f3116ab1e 100644 --- a/internal/dms/biz/data_export_workflow_ce.go +++ b/internal/dms/biz/data_export_workflow_ce.go @@ -39,6 +39,10 @@ func (d *DataExportWorkflowUsecase) ExportDataExportWorkflow(ctx context.Context return errNotDataExportWorkflow } +func (d *DataExportWorkflowUsecase) DownloadOriginalDataExportWorkflow(ctx context.Context, projectUid, workflowUid, currentUserUid, unmaskingWorkflowUid string) (string, []byte, error) { + return "", nil, errNotDataExportWorkflow +} + func (d *DataExportWorkflowUsecase) DownloadDataExportTask(ctx context.Context, userId string, req *dmsV1.DownloadDataExportTaskReq) (bool, string, error) { return false, "", errNotDataExportTask } diff --git a/internal/dms/biz/db_service.go b/internal/dms/biz/db_service.go index 782a56d3e..bdc4d5d8c 100644 --- a/internal/dms/biz/db_service.go +++ b/internal/dms/biz/db_service.go @@ -201,6 +201,26 @@ func NewDBServiceUsecase(log utilLog.Logger, repo DBServiceRepo, maskingTaskRepo } } +// CheckSensitiveDataMaskingTask 查询 data_masking_discovery_tasks 是否存在该数据源的任务行;错误原样返回(用于导出等需失败路径)。 +func (d *DBServiceUsecase) CheckSensitiveDataMaskingTask(ctx context.Context, dbServiceUID string) (bool, error) { + if d == nil || d.maskingTaskRepo == nil || dbServiceUID == "" { + return false, nil + } + return d.maskingTaskRepo.CheckMaskingTaskExist(ctx, dbServiceUID) +} + +// HasSensitiveDataMaskingTask 该数据源是否已存在敏感数据发现任务;查询语义与 CheckSensitiveDataMaskingTask 一致,存储错误时视为未开启并打 debug 日志。 +func (d *DBServiceUsecase) HasSensitiveDataMaskingTask(ctx context.Context, dbServiceUID string) bool { + ok, err := d.CheckSensitiveDataMaskingTask(ctx, dbServiceUID) + if err != nil { + if d != nil && d.log != nil { + d.log.Debugf("CheckSensitiveDataMaskingTask db_service_uid=%s: %v", dbServiceUID, err) + } + return false + } + return ok +} + type BizDBServiceArgs struct { Name string Desc *string diff --git a/internal/dms/service/cloudbeaver.go b/internal/dms/service/cloudbeaver.go index 15f81d4e5..3978fb40d 100644 --- a/internal/dms/service/cloudbeaver.go +++ b/internal/dms/service/cloudbeaver.go @@ -82,7 +82,7 @@ func NewAndInitCloudbeaverService(logger utilLog.Logger, opts *conf.DMSOptions) } } - cloudbeaverUsecase := biz.NewCloudbeaverUsecase(logger, cfg, userUsecase, dbServiceUseCase, opPermissionVerifyUsecase, dmsConfigUseCase, sqlResultMasker, cloudbeaverRepo, dmsProxyTargetRepo, cbOperationLogUsecase, projectUsecase, discoveryTaskRepo, maintenanceTimeUsecase) + cloudbeaverUsecase := biz.NewCloudbeaverUsecase(logger, cfg, userUsecase, dbServiceUseCase, opPermissionVerifyUsecase, dmsConfigUseCase, sqlResultMasker, cloudbeaverRepo, dmsProxyTargetRepo, cbOperationLogUsecase, projectUsecase, maintenanceTimeUsecase) proxyUsecase := biz.NewCloudbeaverProxyUsecase(logger, cloudbeaverUsecase) return &CloudbeaverService{ diff --git a/internal/dms/service/data_export_workflow.go b/internal/dms/service/data_export_workflow.go index e02b6a1e5..f1a2e3eb8 100644 --- a/internal/dms/service/data_export_workflow.go +++ b/internal/dms/service/data_export_workflow.go @@ -248,6 +248,8 @@ func (d *DMSService) GetDataExportWorkflow(ctx context.Context, req *dmsV1.GetDa data.WorkflowRecord.Steps = append(data.WorkflowRecord.Steps, step) } + d.fillGetDataExportUnmaskingWorkflowSummary(ctx, req.DataExportWorkflowUid, data) + return &dmsV1.GetDataExportWorkflowReply{ Data: data, }, nil @@ -337,6 +339,12 @@ func (d *DMSService) ListDataExportTaskSQLs(ctx context.Context, req *dmsV1.List return nil, err } + tasks, err := d.DataExportWorkflowUsecase.BatchGetDataExportTask(ctx, []string{req.DataExportTaskUid}) + if err != nil || len(tasks) == 0 { + return nil, fmt.Errorf("failed to get data export task: %w", err) + } + task := tasks[0] + ret := make([]*dmsV1.ListDataExportTaskSQL, len(taskRecords)) for i, w := range taskRecords { ret[i] = &dmsV1.ListDataExportTaskSQL{ @@ -346,6 +354,11 @@ func (d *DMSService) ListDataExportTaskSQLs(ctx context.Context, req *dmsV1.List ExportResult: w.ExportResult, ExportSQLType: w.ExportSQLType, } + if d.UnmaskingWorkflowUsecase != nil { + lineage, snapshot := d.UnmaskingWorkflowUsecase.AnalyzeLineageAndBuildMaskingSnapshot(ctx, req.ProjectUid, task.DBServiceUid, task.DatabaseName, w.ExportSQL) + ret[i].LineageAnalysisSnapshot = lineage + ret[i].MaskingConfigSnapshot = snapshot + } if w.AuditSQLResults != nil { for _, result := range w.AuditSQLResults { ret[i].AuditSQLResult = append(ret[i].AuditSQLResult, dmsV1.AuditSQLResult{ diff --git a/internal/dms/service/data_export_workflow_ce.go b/internal/dms/service/data_export_workflow_ce.go new file mode 100644 index 000000000..67de1d381 --- /dev/null +++ b/internal/dms/service/data_export_workflow_ce.go @@ -0,0 +1,16 @@ +//go:build !dms + +package service + +import ( + "context" + "errors" + + dmsV1 "github.com/actiontech/dms/api/dms/service/v1" +) + +func (d *DMSService) DownloadOriginalDataExportWorkflow(ctx context.Context, req *dmsV1.DownloadOriginalDataExportWorkflowReq, currentUserUid string) (string, []byte, error) { + return "", nil, errors.New("export original data is an enterprise version function") +} + +func (d *DMSService) fillGetDataExportUnmaskingWorkflowSummary(_ context.Context, _ string, _ *dmsV1.GetDataExportWorkflow) {} diff --git a/internal/dms/service/db_structures_ce.go b/internal/dms/service/db_structures_ce.go new file mode 100644 index 000000000..de768ef09 --- /dev/null +++ b/internal/dms/service/db_structures_ce.go @@ -0,0 +1,14 @@ +//go:build !dms + +package service + +import ( + "context" + + dmsV1 "github.com/actiontech/dms/api/dms/service/v1" +) + +func (d *DMSService) ListTableColumns(ctx context.Context, req *dmsV1.ListTableColumnsReq) (*dmsV1.ListTableColumnsReply, error) { + return nil, errNotSupportDataMasking +} + diff --git a/internal/dms/service/service.go b/internal/dms/service/service.go index 46c3fb0c3..f88b4ac40 100644 --- a/internal/dms/service/service.go +++ b/internal/dms/service/service.go @@ -41,6 +41,7 @@ type DMSService struct { LicenseUsecase *biz.LicenseUsecase ClusterUsecase *biz.ClusterUsecase DataExportWorkflowUsecase *biz.DataExportWorkflowUsecase + UnmaskingWorkflowUsecase *unmaskingWorkflowUsecase CbOperationLogUsecase *biz.CbOperationLogUsecase DataMaskingUsecase *dataMaskingUsecase FunctionSupportRegistry *biz.FunctionSupportRegistry @@ -153,7 +154,11 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer workflowRepo := storage.NewWorkflowRepo(logger, st) dataExportMaskingConfigRepo := initDataExportMaskingConfigRepo(logger, st) dataExportMaskingRuleRepo := initDataExportMaskingRuleRepo(logger, st) - DataExportWorkflowUsecase := biz.NewDataExportWorkflowUsecase(logger, tx, workflowRepo, dataExportTaskRepo, dbServiceRepo, dataExportMaskingConfigRepo, dataExportMaskingRuleRepo, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo, clusterUsecase, webhookConfigurationUsecase, userUsecase, systemVariableUsecase, discoveryTaskRepo, fmt.Sprintf("%s:%d", opts.ReportHost, opts.APIServiceOpts.Port)) + unmaskingWorkflowUsecase, err := initUnmaskingWorkflowUsecase(logger, st, dmsProxyTargetRepo, opPermissionVerifyUsecase, userUsecase, dbServiceUseCase) + if err != nil { + return nil, fmt.Errorf("failed to initialize unmasking workflow usecase: %v", err) + } + DataExportWorkflowUsecase := biz.NewDataExportWorkflowUsecase(logger, tx, workflowRepo, dataExportTaskRepo, dbServiceRepo, dataExportMaskingConfigRepo, dataExportMaskingRuleRepo, opPermissionVerifyUsecase, projectUsecase, dmsProxyTargetRepo, clusterUsecase, webhookConfigurationUsecase, userUsecase, systemVariableUsecase, dbServiceUseCase, unmaskingWorkflowUsecase, fmt.Sprintf("%s:%d", opts.ReportHost, opts.APIServiceOpts.Port)) dataMaskingUsecase, stopDataMaskingScheduler, err := initDataMaskingUsecase(logger, st, dbServiceUseCase, clusterUsecase, dmsProxyTargetRepo) if err != nil { return nil, fmt.Errorf("failed to initialize data masking usecase: %v", err) @@ -202,6 +207,7 @@ func NewAndInitDMSService(logger utilLog.Logger, opts *conf.DMSOptions) (*DMSSer LicenseUsecase: LicenseUsecase, ClusterUsecase: clusterUsecase, DataExportWorkflowUsecase: DataExportWorkflowUsecase, + UnmaskingWorkflowUsecase: unmaskingWorkflowUsecase, CbOperationLogUsecase: CbOperationLogUsecase, DataMaskingUsecase: dataMaskingUsecase, FunctionSupportRegistry: functionSupportRegistry, diff --git a/internal/dms/service/sql_workbench_result_masker_ce.go b/internal/dms/service/sql_workbench_result_masker_ce.go new file mode 100644 index 000000000..de7219276 --- /dev/null +++ b/internal/dms/service/sql_workbench_result_masker_ce.go @@ -0,0 +1,14 @@ +//go:build !dms + +package service + +import ( + "github.com/actiontech/dms/internal/dms/storage" + sqlresultmasker "github.com/actiontech/dms/internal/sql_workbench/sqlresultmasker" + utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log" +) + +// NewSQLWorkbenchSQLResultMasker is a no-op in builds without the dms data-masking stack. +func NewSQLWorkbenchSQLResultMasker(_ utilLog.Logger, _ *storage.Storage) (sqlresultmasker.SQLResultMasker, error) { + return nil, nil +} diff --git a/internal/dms/service/unmasking_workflow_ce.go b/internal/dms/service/unmasking_workflow_ce.go new file mode 100644 index 000000000..07923338e --- /dev/null +++ b/internal/dms/service/unmasking_workflow_ce.go @@ -0,0 +1,54 @@ +//go:build !dms + +package service + +import ( + "context" + "errors" + + v1 "github.com/actiontech/dms/api/dms/service/v1" + "github.com/actiontech/dms/internal/data_masking/biz" + dmsBiz "github.com/actiontech/dms/internal/dms/biz" + "github.com/actiontech/dms/internal/dms/storage" + utilLog "github.com/actiontech/dms/pkg/dms-common/pkg/log" +) + +var errNotSupportUnmaskingWorkflow = errors.New("unmasking workflow related functions are enterprise version functions") + +type unmaskingWorkflowUsecase = biz.UnmaskingWorkflowUsecase + +func initUnmaskingWorkflowUsecase(_ utilLog.Logger, _ *storage.Storage, _ dmsBiz.ProxyTargetRepo, _ *dmsBiz.OpPermissionVerifyUsecase, _ *dmsBiz.UserUsecase, _ *dmsBiz.DBServiceUsecase) (*unmaskingWorkflowUsecase, error) { + return nil, nil +} + +func (d *DMSService) CreateUnmaskingWorkflow(ctx context.Context, req *v1.CreateUnmaskingWorkflowReq, currentUserUid string) (*v1.CreateUnmaskingWorkflowReply, error) { + return nil, errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) GetUnmaskingWorkflow(ctx context.Context, req *v1.GetUnmaskingWorkflowReq, currentUserUid string) (*v1.GetUnmaskingWorkflowReply, error) { + return nil, errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) ListUnmaskingWorkflows(ctx context.Context, req *v1.ListUnmaskingWorkflowsReq, currentUserUid string) (*v1.ListUnmaskingWorkflowsReply, error) { + return nil, errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) ApproveUnmaskingWorkflow(ctx context.Context, req *v1.ApproveUnmaskingWorkflowReq, currentUserUid string) error { + return errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) RejectUnmaskingWorkflow(ctx context.Context, req *v1.RejectUnmaskingWorkflowReq, currentUserUid string) error { + return errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) CancelUnmaskingWorkflow(ctx context.Context, req *v1.CancelUnmaskingWorkflowReq, currentUserUid string) error { + return errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) ActivateUnmaskingWorkflowView(ctx context.Context, req *v1.ActivateUnmaskingWorkflowViewReq, currentUserUid string) (*v1.ActivateUnmaskingWorkflowViewReply, error) { + return nil, errNotSupportUnmaskingWorkflow +} + +func (d *DMSService) GetUnmaskingWorkflowPlaintext(ctx context.Context, req *v1.GetUnmaskingWorkflowPlaintextReq, currentUserUid string) (*v1.GetUnmaskingWorkflowPlaintextReply, error) { + return nil, errNotSupportUnmaskingWorkflow +} diff --git a/internal/pkg/locale/active.en.toml b/internal/pkg/locale/active.en.toml index 9e362dfc8..06e8c616b 100644 --- a/internal/pkg/locale/active.en.toml +++ b/internal/pkg/locale/active.en.toml @@ -179,6 +179,15 @@ OpRecordDataExportCreate = "Create data export workflow" OpRecordDataExportCreateWithName = "Create data export workflow %s" OpRecordDataExportExportWithName = "Execute data export %s" OpRecordDataExportRejectWithName = "Reject data export workflow %s" +OpRecordUnmaskingAPICreate = "Submit unmasking workflow application" +OpRecordUnmaskingApproveWithWorkflowUID = "Approve unmasking workflow %s" +OpRecordUnmaskingCancelWithWorkflowUID = "Cancel unmasking workflow %s" +OpRecordUnmaskingDownloadOriginalWithWorkflowUID = "Download original data %s" +OpRecordUnmaskingRejectWithWorkflowUID = "Reject unmasking workflow %s" +OpRecordUnmaskingSubmitWithWorkflowUID = "Submit unmasking workflow %s" +OpRecordUnmaskingUnknownWithWorkflowUID = "Unmasking workflow action %s (%s)" +OpRecordUnmaskingViewDetailWithWorkflowUID = "View unmasking workflow detail %s" +OpRecordUnmaskingViewOriginalWithWorkflowUID = "View full original data (SQL) %s" OpRecordMemberCreate = "Add member" OpRecordMemberCreateWithName = "Add member %s" OpRecordMemberDelete = "Delete member %s" diff --git a/internal/pkg/locale/active.zh.toml b/internal/pkg/locale/active.zh.toml index 2186794c6..80b05e6a4 100644 --- a/internal/pkg/locale/active.zh.toml +++ b/internal/pkg/locale/active.zh.toml @@ -179,6 +179,15 @@ OpRecordDataExportCreate = "创建数据导出工单" OpRecordDataExportCreateWithName = "创建数据导出工单 %s" OpRecordDataExportExportWithName = "执行数据导出 %s" OpRecordDataExportRejectWithName = "驳回数据导出工单 %s" +OpRecordUnmaskingAPICreate = "提交查看原文工单申请" +OpRecordUnmaskingApproveWithWorkflowUID = "审批通过查看原文工单 %s" +OpRecordUnmaskingCancelWithWorkflowUID = "撤回查看原文工单 %s" +OpRecordUnmaskingDownloadOriginalWithWorkflowUID = "下载原文数据 %s" +OpRecordUnmaskingRejectWithWorkflowUID = "驳回查看原文工单 %s" +OpRecordUnmaskingSubmitWithWorkflowUID = "提交查看原文工单 %s" +OpRecordUnmaskingUnknownWithWorkflowUID = "查看原文工单操作 %s (%s)" +OpRecordUnmaskingViewDetailWithWorkflowUID = "查看查看原文工单详情 %s" +OpRecordUnmaskingViewOriginalWithWorkflowUID = "查看原文(SQL)%s" OpRecordMemberCreate = "添加成员" OpRecordMemberCreateWithName = "添加成员 %s" OpRecordMemberDelete = "删除成员 %s" diff --git a/internal/pkg/locale/message_zh.go b/internal/pkg/locale/message_zh.go index 2ec182920..e0e6a66dc 100644 --- a/internal/pkg/locale/message_zh.go +++ b/internal/pkg/locale/message_zh.go @@ -267,50 +267,65 @@ var ( // Operation Record var ( - OpRecordUserCreate = &i18n.Message{ID: "OpRecordUserCreate", Other: "创建用户"} - OpRecordUserCreateWithName = &i18n.Message{ID: "OpRecordUserCreateWithName", Other: "创建用户 %s"} - OpRecordCurrentUserUpdate = &i18n.Message{ID: "OpRecordCurrentUserUpdate", Other: "更新个人中心账号基本信息"} - OpRecordUserUpdate = &i18n.Message{ID: "OpRecordUserUpdate", Other: "更新用户 %s"} - OpRecordUserDelete = &i18n.Message{ID: "OpRecordUserDelete", Other: "删除用户 %s"} - OpRecordMemberCreate = &i18n.Message{ID: "OpRecordMemberCreate", Other: "添加成员"} - OpRecordMemberCreateWithName = &i18n.Message{ID: "OpRecordMemberCreateWithName", Other: "添加成员 %s"} - OpRecordMemberUpdate = &i18n.Message{ID: "OpRecordMemberUpdate", Other: "更新成员 %s"} - OpRecordMemberDelete = &i18n.Message{ID: "OpRecordMemberDelete", Other: "删除成员 %s"} - OpRecordMemberGroupCreate = &i18n.Message{ID: "OpRecordMemberGroupCreate", Other: "添加成员组"} - OpRecordMemberGroupCreateWithName = &i18n.Message{ID: "OpRecordMemberGroupCreateWithName", Other: "添加成员组 %s"} - OpRecordMemberGroupUpdate = &i18n.Message{ID: "OpRecordMemberGroupUpdate", Other: "更新成员组 %s"} - OpRecordMemberGroupDelete = &i18n.Message{ID: "OpRecordMemberGroupDelete", Other: "删除成员组 %s"} - OpRecordRoleCreate = &i18n.Message{ID: "OpRecordRoleCreate", Other: "创建角色"} - OpRecordRoleCreateWithName = &i18n.Message{ID: "OpRecordRoleCreateWithName", Other: "创建角色 %s"} - OpRecordRoleUpdate = &i18n.Message{ID: "OpRecordRoleUpdate", Other: "更新角色 %s"} - OpRecordRoleDelete = &i18n.Message{ID: "OpRecordRoleDelete", Other: "删除角色 %s"} - OpRecordProjectCreate = &i18n.Message{ID: "OpRecordProjectCreate", Other: "创建项目"} - OpRecordProjectCreateWithName = &i18n.Message{ID: "OpRecordProjectCreateWithName", Other: "创建项目 %s"} - OpRecordProjectUpdate = &i18n.Message{ID: "OpRecordProjectUpdate", Other: "更新项目 %s"} - OpRecordProjectDelete = &i18n.Message{ID: "OpRecordProjectDelete", Other: "删除项目 %s"} - OpRecordProjectArchive = &i18n.Message{ID: "OpRecordProjectArchive", Other: "归档项目 %s"} - OpRecordProjectUnarchive = &i18n.Message{ID: "OpRecordProjectUnarchive", Other: "取消归档项目 %s"} - OpRecordDBServiceCreate = &i18n.Message{ID: "OpRecordDBServiceCreate", Other: "创建数据源"} - OpRecordDBServiceCreateWithName = &i18n.Message{ID: "OpRecordDBServiceCreateWithName", Other: "创建数据源 %s"} - OpRecordDBServiceUpdate = &i18n.Message{ID: "OpRecordDBServiceUpdate", Other: "更新数据源 %s"} - OpRecordDBServiceDelete = &i18n.Message{ID: "OpRecordDBServiceDelete", Other: "删除数据源 %s"} - OpRecordDBServiceImport = &i18n.Message{ID: "OpRecordDBServiceImport", Other: "导入数据源"} - OpRecordConfigLogin = &i18n.Message{ID: "OpRecordConfigLogin", Other: "更新登录配置"} - OpRecordConfigOAuth2 = &i18n.Message{ID: "OpRecordConfigOAuth2", Other: "更新OAuth2配置"} - OpRecordConfigLDAP = &i18n.Message{ID: "OpRecordConfigLDAP", Other: "更新LDAP配置"} - OpRecordConfigSMTP = &i18n.Message{ID: "OpRecordConfigSMTP", Other: "更新SMTP配置"} - OpRecordConfigWechat = &i18n.Message{ID: "OpRecordConfigWechat", Other: "更新企业微信配置"} - OpRecordConfigFeishu = &i18n.Message{ID: "OpRecordConfigFeishu", Other: "更新飞书配置"} - OpRecordConfigWebhook = &i18n.Message{ID: "OpRecordConfigWebhook", Other: "更新Webhook配置"} - OpRecordConfigSms = &i18n.Message{ID: "OpRecordConfigSms", Other: "更新短信配置"} - OpRecordConfigSystemVariables = &i18n.Message{ID: "OpRecordConfigSystemVariables", Other: "更新系统变量配置"} - OpRecordConfigCompanyNotice = &i18n.Message{ID: "OpRecordConfigCompanyNotice", Other: "更新系统公告"} - OpRecordDataExportCreate = &i18n.Message{ID: "OpRecordDataExportCreate", Other: "创建数据导出工单"} - OpRecordDataExportCreateWithName = &i18n.Message{ID: "OpRecordDataExportCreateWithName", Other: "创建数据导出工单 %s"} - OpRecordDataExportApproveWithName = &i18n.Message{ID: "OpRecordDataExportApproveWithName", Other: "审批通过数据导出工单 %s"} - OpRecordDataExportRejectWithName = &i18n.Message{ID: "OpRecordDataExportRejectWithName", Other: "驳回数据导出工单 %s"} - OpRecordDataExportExportWithName = &i18n.Message{ID: "OpRecordDataExportExportWithName", Other: "执行数据导出 %s"} - OpRecordDataExportCancel = &i18n.Message{ID: "OpRecordDataExportCancel", Other: "取消数据导出工单"} - OpRecordDataExportCancelWithName = &i18n.Message{ID: "OpRecordDataExportCancelWithName", Other: "取消数据导出工单 %s"} - OpRecordUserUpdateBWP = &i18n.Message{ID: "OpRecordUserUpdateBWP", Other: "修改用户 %s 的业务写权:%s -> %s"} + OpRecordUserCreate = &i18n.Message{ID: "OpRecordUserCreate", Other: "创建用户"} + OpRecordUserCreateWithName = &i18n.Message{ID: "OpRecordUserCreateWithName", Other: "创建用户 %s"} + OpRecordCurrentUserUpdate = &i18n.Message{ID: "OpRecordCurrentUserUpdate", Other: "更新个人中心账号基本信息"} + OpRecordUserUpdate = &i18n.Message{ID: "OpRecordUserUpdate", Other: "更新用户 %s"} + OpRecordUserDelete = &i18n.Message{ID: "OpRecordUserDelete", Other: "删除用户 %s"} + OpRecordMemberCreate = &i18n.Message{ID: "OpRecordMemberCreate", Other: "添加成员"} + OpRecordMemberCreateWithName = &i18n.Message{ID: "OpRecordMemberCreateWithName", Other: "添加成员 %s"} + OpRecordMemberUpdate = &i18n.Message{ID: "OpRecordMemberUpdate", Other: "更新成员 %s"} + OpRecordMemberDelete = &i18n.Message{ID: "OpRecordMemberDelete", Other: "删除成员 %s"} + OpRecordMemberGroupCreate = &i18n.Message{ID: "OpRecordMemberGroupCreate", Other: "添加成员组"} + OpRecordMemberGroupCreateWithName = &i18n.Message{ID: "OpRecordMemberGroupCreateWithName", Other: "添加成员组 %s"} + OpRecordMemberGroupUpdate = &i18n.Message{ID: "OpRecordMemberGroupUpdate", Other: "更新成员组 %s"} + OpRecordMemberGroupDelete = &i18n.Message{ID: "OpRecordMemberGroupDelete", Other: "删除成员组 %s"} + OpRecordRoleCreate = &i18n.Message{ID: "OpRecordRoleCreate", Other: "创建角色"} + OpRecordRoleCreateWithName = &i18n.Message{ID: "OpRecordRoleCreateWithName", Other: "创建角色 %s"} + OpRecordRoleUpdate = &i18n.Message{ID: "OpRecordRoleUpdate", Other: "更新角色 %s"} + OpRecordRoleDelete = &i18n.Message{ID: "OpRecordRoleDelete", Other: "删除角色 %s"} + OpRecordProjectCreate = &i18n.Message{ID: "OpRecordProjectCreate", Other: "创建项目"} + OpRecordProjectCreateWithName = &i18n.Message{ID: "OpRecordProjectCreateWithName", Other: "创建项目 %s"} + OpRecordProjectUpdate = &i18n.Message{ID: "OpRecordProjectUpdate", Other: "更新项目 %s"} + OpRecordProjectDelete = &i18n.Message{ID: "OpRecordProjectDelete", Other: "删除项目 %s"} + OpRecordProjectArchive = &i18n.Message{ID: "OpRecordProjectArchive", Other: "归档项目 %s"} + OpRecordProjectUnarchive = &i18n.Message{ID: "OpRecordProjectUnarchive", Other: "取消归档项目 %s"} + OpRecordDBServiceCreate = &i18n.Message{ID: "OpRecordDBServiceCreate", Other: "创建数据源"} + OpRecordDBServiceCreateWithName = &i18n.Message{ID: "OpRecordDBServiceCreateWithName", Other: "创建数据源 %s"} + OpRecordDBServiceUpdate = &i18n.Message{ID: "OpRecordDBServiceUpdate", Other: "更新数据源 %s"} + OpRecordDBServiceDelete = &i18n.Message{ID: "OpRecordDBServiceDelete", Other: "删除数据源 %s"} + OpRecordDBServiceImport = &i18n.Message{ID: "OpRecordDBServiceImport", Other: "导入数据源"} + OpRecordConfigLogin = &i18n.Message{ID: "OpRecordConfigLogin", Other: "更新登录配置"} + OpRecordConfigOAuth2 = &i18n.Message{ID: "OpRecordConfigOAuth2", Other: "更新OAuth2配置"} + OpRecordConfigLDAP = &i18n.Message{ID: "OpRecordConfigLDAP", Other: "更新LDAP配置"} + OpRecordConfigSMTP = &i18n.Message{ID: "OpRecordConfigSMTP", Other: "更新SMTP配置"} + OpRecordConfigWechat = &i18n.Message{ID: "OpRecordConfigWechat", Other: "更新企业微信配置"} + OpRecordConfigFeishu = &i18n.Message{ID: "OpRecordConfigFeishu", Other: "更新飞书配置"} + OpRecordConfigWebhook = &i18n.Message{ID: "OpRecordConfigWebhook", Other: "更新Webhook配置"} + OpRecordConfigSms = &i18n.Message{ID: "OpRecordConfigSms", Other: "更新短信配置"} + OpRecordConfigSystemVariables = &i18n.Message{ID: "OpRecordConfigSystemVariables", Other: "更新系统变量配置"} + OpRecordConfigCompanyNotice = &i18n.Message{ID: "OpRecordConfigCompanyNotice", Other: "更新系统公告"} + OpRecordDataExportCreate = &i18n.Message{ID: "OpRecordDataExportCreate", Other: "创建数据导出工单"} + OpRecordDataExportCreateWithName = &i18n.Message{ID: "OpRecordDataExportCreateWithName", Other: "创建数据导出工单 %s"} + OpRecordDataExportApproveWithName = &i18n.Message{ID: "OpRecordDataExportApproveWithName", Other: "审批通过数据导出工单 %s"} + OpRecordDataExportRejectWithName = &i18n.Message{ID: "OpRecordDataExportRejectWithName", Other: "驳回数据导出工单 %s"} + OpRecordDataExportExportWithName = &i18n.Message{ID: "OpRecordDataExportExportWithName", Other: "执行数据导出 %s"} + OpRecordDataExportCancel = &i18n.Message{ID: "OpRecordDataExportCancel", Other: "取消数据导出工单"} + OpRecordDataExportCancelWithName = &i18n.Message{ID: "OpRecordDataExportCancelWithName", Other: "取消数据导出工单 %s"} + OpRecordUserUpdateBWP = &i18n.Message{ID: "OpRecordUserUpdateBWP", Other: "修改用户 %s 的业务写权:%s -> %s"} + OpRecordUnmaskingAPICreate = &i18n.Message{ID: "OpRecordUnmaskingAPICreate", Other: "提交查看原文工单申请"} + OpRecordUnmaskingSubmitWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingSubmitWithWorkflowUID", Other: "提交查看原文工单 %s"} + OpRecordUnmaskingApproveWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingApproveWithWorkflowUID", Other: "审批通过查看原文工单 %s"} + OpRecordUnmaskingRejectWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingRejectWithWorkflowUID", Other: "驳回查看原文工单 %s"} + OpRecordUnmaskingViewDetailWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingViewDetailWithWorkflowUID", Other: "查看查看原文工单详情 %s"} + OpRecordUnmaskingViewOriginalWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingViewOriginalWithWorkflowUID", Other: "查看原文(SQL)%s"} + OpRecordUnmaskingDownloadOriginalWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingDownloadOriginalWithWorkflowUID", Other: "下载原文数据 %s"} + OpRecordUnmaskingCancelWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingCancelWithWorkflowUID", Other: "撤回查看原文工单 %s"} + OpRecordUnmaskingUnknownWithWorkflowUID = &i18n.Message{ID: "OpRecordUnmaskingUnknownWithWorkflowUID", Other: "查看原文工单操作 %s (%s)"} +) + +// Unmasking Workflow +var ( + SqlWorkbenchUnmaskingNoPermissionErr = &i18n.Message{ID: "SqlWorkbenchUnmaskingNoPermissionErr", Other: "您没有查看原文的权限,请提交查看原文工单"} + SqlWorkbenchUnmaskingWorkflowNotFoundErr = &i18n.Message{ID: "SqlWorkbenchUnmaskingWorkflowNotFoundErr", Other: "未找到对应的查看原文工单"} ) diff --git a/internal/sql_workbench/service/data_masking_middleware.go b/internal/sql_workbench/service/data_masking_middleware.go new file mode 100644 index 000000000..621dca64c --- /dev/null +++ b/internal/sql_workbench/service/data_masking_middleware.go @@ -0,0 +1,15 @@ +package sql_workbench + +import ( + dataMaskingBiz "github.com/actiontech/dms/internal/data_masking/biz" + "github.com/actiontech/dms/internal/dms/biz" + "github.com/actiontech/dms/internal/sql_workbench/sqlresultmasker" +) + +// DataMaskingMiddlewareConfig 配置脱敏中间件 +type DataMaskingMiddlewareConfig struct { + SqlResultMasker sqlresultmasker.SQLResultMasker + DBServiceUsecase *biz.DBServiceUsecase + SqlWorkbenchService *SqlWorkbenchService + UnmaskingWorkflowUsecase *dataMaskingBiz.UnmaskingWorkflowUsecase +} diff --git a/internal/sql_workbench/service/data_masking_middleware_ce.go b/internal/sql_workbench/service/data_masking_middleware_ce.go new file mode 100644 index 000000000..5f10b9047 --- /dev/null +++ b/internal/sql_workbench/service/data_masking_middleware_ce.go @@ -0,0 +1,24 @@ +//go:build !enterprise +// +build !enterprise + +package sql_workbench + +import "github.com/labstack/echo/v4" + +// GetDataMaskingMiddleware 返回用于脱敏的中间件 +func GetDataMaskingMiddleware(config DataMaskingMiddlewareConfig) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + return next(c) + } + } +} + +// GetUnmaskingWorkflowMiddleware 处理获批后查看原文,执行 SQL 之前进行替换 +func GetUnmaskingWorkflowMiddleware(config DataMaskingMiddlewareConfig) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + return next(c) + } + } +} diff --git a/internal/sql_workbench/service/sql_workbench_service.go b/internal/sql_workbench/service/sql_workbench_service.go index e2ffff1b7..f237ced19 100644 --- a/internal/sql_workbench/service/sql_workbench_service.go +++ b/internal/sql_workbench/service/sql_workbench_service.go @@ -23,10 +23,11 @@ import ( "github.com/actiontech/dms/internal/dms/biz" pkgConst "github.com/actiontech/dms/internal/dms/pkg/constant" "github.com/actiontech/dms/internal/dms/storage" - "github.com/actiontech/dms/internal/pkg/locale" dbmodel "github.com/actiontech/dms/internal/dms/storage/model" + "github.com/actiontech/dms/internal/pkg/locale" "github.com/actiontech/dms/internal/sql_workbench/client" config "github.com/actiontech/dms/internal/sql_workbench/config" + "github.com/actiontech/dms/internal/sql_workbench/sqlresultmasker" "github.com/actiontech/dms/pkg/dms-common/api/jwt" "github.com/actiontech/dms/pkg/dms-common/i18nPkg" _const "github.com/actiontech/dms/pkg/dms-common/pkg/const" @@ -102,6 +103,15 @@ type SqlWorkbenchService struct { sqlWorkbenchDatasourceRepo biz.SqlWorkbenchDatasourceRepo proxyTargetRepo biz.ProxyTargetRepo cbOperationLogUsecase *biz.CbOperationLogUsecase + sqlResultMasker sqlresultmasker.SQLResultMasker +} + +func (sqlWorkbenchService *SqlWorkbenchService) SetSqlResultMasker(masker sqlresultmasker.SQLResultMasker) { + sqlWorkbenchService.sqlResultMasker = masker +} + +func (sqlWorkbenchService *SqlWorkbenchService) GetSqlResultMasker() sqlresultmasker.SQLResultMasker { + return sqlWorkbenchService.sqlResultMasker } func NewAndInitSqlWorkbenchService(logger utilLog.Logger, opts *conf.DMSOptions) (*SqlWorkbenchService, error) { @@ -1046,15 +1056,18 @@ func (sqlWorkbenchService *SqlWorkbenchService) AuditMiddleware() echo.Middlewar c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 解析请求体获取 SQL 和 datasource ID + // 注意:解析仅服务于审核辅助路径,解析失败不应直接阻塞用户的 SQL 执行; + // 否则一旦中间件辅助能力出错(如 sid 解码失败),用户连查询都跑不了。 + // 真正的「未启用审核 / 审核失败」等强策略仍由后续分支按既有 fail-closed 处理。 sql, datasourceID, err := sqlWorkbenchService.parseStreamExecuteRequest(bodyBytes) if err != nil { - sqlWorkbenchService.log.Errorf("failed to parse streamExecute request, skipping audit: %v", err) - return errors.New(locale.Bundle.LocalizeMsgByCtx(c.Request().Context(), locale.SqlWorkbenchAuditParseReqErr)) + sqlWorkbenchService.log.Warnf("failed to parse streamExecute request, skipping audit: %v", err) + return next(c) } if sql == "" || datasourceID == "" { - sqlWorkbenchService.log.Debugf("SQL or datasource ID is empty, skipping audit") - return errors.New(locale.Bundle.LocalizeMsgByCtx(c.Request().Context(), locale.SqlWorkbenchAuditMissingSQLOrDatasourceErr)) + sqlWorkbenchService.log.Warnf("SQL or datasource ID is empty, skipping audit") + return next(c) } // 获取当前用户 ID @@ -1149,8 +1162,9 @@ func (sqlWorkbenchService *SqlWorkbenchService) parseSidToDatasourceID(sid strin sid = sid[:idx] } - // 解码 base64 - decodedBytes, err := base64.StdEncoding.DecodeString(sid) + // ODC 服务端使用 Base64.getUrlEncoder() 生成 sessionId(URL-safe,包含 '-'/'_'), + // 这里必须用 URLEncoding 解码,否则遇到 '-'/'_' 会报 illegal base64 data。 + decodedBytes, err := base64.URLEncoding.DecodeString(sid) if err != nil { return "", fmt.Errorf("failed to decode base64 sid: %v", err) } @@ -1438,14 +1452,14 @@ func extractSessionID(path string) string { // executeAndAddAuditResult 执行真实请求并添加审核结果 func (sqlWorkbenchService *SqlWorkbenchService) executeAndAddAuditResult(c echo.Context, next echo.HandlerFunc, auditResult *cloudbeaver.AuditSQLReply, dbService *biz.DBService) error { // 创建响应拦截器 - srw := newStreamExecuteResponseWriter(c) + srw := NewStreamExecuteResponseWriter(c) cloudbeaverResBuf := srw.Buffer c.Response().Writer = srw defer func() { // 在 defer 中处理响应 - if srw.status != 0 { - srw.original.WriteHeader(srw.status) + if srw.Status != 0 { + srw.Original.WriteHeader(srw.Status) } // 读取响应内容 @@ -1458,13 +1472,13 @@ func (sqlWorkbenchService *SqlWorkbenchService) executeAndAddAuditResult(c echo. responseBytes, wasGzip, err := sqlWorkbenchService.decodeResponseBody(cloudbeaverResBuf.Bytes(), srw.Header().Get("Content-Encoding")) if err != nil { sqlWorkbenchService.log.Debugf("Failed to decode response body, returning original response: %v", err) - srw.original.Write(cloudbeaverResBuf.Bytes()) + srw.Original.Write(cloudbeaverResBuf.Bytes()) return } // 如果解压过,先移除 Content-Encoding,后续根据需要重新设置 if wasGzip { - srw.original.Header().Del("Content-Encoding") + srw.Original.Header().Del("Content-Encoding") } // 解析响应 JSON @@ -1472,7 +1486,7 @@ func (sqlWorkbenchService *SqlWorkbenchService) executeAndAddAuditResult(c echo. if err := json.Unmarshal(responseBytes, &responseBody); err != nil { sqlWorkbenchService.log.Debugf("Failed to unmarshal response, returning original response: %v", err) // 如果解析失败,直接返回原始响应 - srw.original.Write(cloudbeaverResBuf.Bytes()) + srw.Original.Write(cloudbeaverResBuf.Bytes()) return } @@ -1486,7 +1500,7 @@ func (sqlWorkbenchService *SqlWorkbenchService) executeAndAddAuditResult(c echo. modifiedResponse, err := json.Marshal(responseBody) if err != nil { sqlWorkbenchService.log.Errorf("Failed to marshal modified response: %v", err) - srw.original.Write(cloudbeaverResBuf.Bytes()) + srw.Original.Write(cloudbeaverResBuf.Bytes()) return } @@ -1495,26 +1509,26 @@ func (sqlWorkbenchService *SqlWorkbenchService) executeAndAddAuditResult(c echo. encoded, err := sqlWorkbenchService.encodeResponseBody(modifiedResponse) if err != nil { sqlWorkbenchService.log.Errorf("Failed to re-encode gzip response: %v", err) - srw.original.Write(cloudbeaverResBuf.Bytes()) + srw.Original.Write(cloudbeaverResBuf.Bytes()) return } finalResponse = encoded - srw.original.Header().Set("Content-Encoding", "gzip") + srw.Original.Header().Set("Content-Encoding", "gzip") } // 更新 Content-Length - header := srw.original.Header() + header := srw.Original.Header() header.Set("Content-Length", fmt.Sprintf("%d", len(finalResponse))) // 如果拦截过程中未显式写入状态码,默认使用 200 - statusCode := srw.status + statusCode := srw.Status if statusCode == 0 { statusCode = http.StatusOK } - srw.original.WriteHeader(statusCode) + srw.Original.WriteHeader(statusCode) // 写入修改后的响应 - if _, err := srw.original.Write(finalResponse); err != nil { + if _, err := srw.Original.Write(finalResponse); err != nil { sqlWorkbenchService.log.Errorf("Failed to write modified response: %v", err) } }() @@ -1747,34 +1761,32 @@ type SQLEAuditResultSummary struct { PassRate float64 `json:"pass_rate"` } -// streamExecuteResponseWriter 响应拦截器,用于捕获响应内容 -type streamExecuteResponseWriter struct { +// StreamExecuteResponseWriter 响应拦截器,用于捕获响应内容 +type StreamExecuteResponseWriter struct { echo.Response Buffer *bytes.Buffer - original http.ResponseWriter - status int + Original http.ResponseWriter + Status int } -func newStreamExecuteResponseWriter(c echo.Context) *streamExecuteResponseWriter { +func NewStreamExecuteResponseWriter(c echo.Context) *StreamExecuteResponseWriter { buf := new(bytes.Buffer) - return &streamExecuteResponseWriter{ + return &StreamExecuteResponseWriter{ Response: *c.Response(), Buffer: buf, - original: c.Response().Writer, + Original: c.Response().Writer, } } -func (w *streamExecuteResponseWriter) Write(b []byte) (int, error) { +func (w *StreamExecuteResponseWriter) Write(b []byte) (int, error) { // 如果未设置状态码,则补默认值 - if w.status == 0 { + if w.Status == 0 { w.WriteHeader(http.StatusOK) } // 写入 buffer,不立即写给客户端 return w.Buffer.Write(b) } -func (w *streamExecuteResponseWriter) WriteHeader(code int) { - w.status = code +func (w *StreamExecuteResponseWriter) WriteHeader(code int) { + w.Status = code } - - diff --git a/internal/sql_workbench/sqlresultmasker/masker.go b/internal/sql_workbench/sqlresultmasker/masker.go new file mode 100644 index 000000000..780c02f30 --- /dev/null +++ b/internal/sql_workbench/sqlresultmasker/masker.go @@ -0,0 +1,18 @@ +package sqlresultmasker + +import "context" + +// MaskWorkbenchResultsArgs carries tabular SQL workbench (ODC) result data and masking scope. Rows are masked in place. +type MaskWorkbenchResultsArgs struct { + Rows [][]any `json:"rows"` + ColumnNames []string `json:"column_names"` + SQL string `json:"sql"` + DBServiceUID string `json:"db_service_uid"` + SchemaName string `json:"schema_name"` + ProjectUID string `json:"project_uid"` +} + +// SQLResultMasker masks SQL workbench (ODC) tabular result rows in place and reports which columns were masked. +type SQLResultMasker interface { + MaskSQLWorkbenchResults(ctx context.Context, args *MaskWorkbenchResultsArgs) (map[string]bool, error) +} diff --git a/scripts/verify_build_editions.sh b/scripts/verify_build_editions.sh new file mode 100755 index 000000000..4e238610b --- /dev/null +++ b/scripts/verify_build_editions.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# 提交前校验:社区版 / 试用版 / 企业版 / DMS 企业版 四种构建标签组合均能成功编译(与 Makefile install 一致)。 +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +readonly RED=$'\033[0;31m' +readonly GREEN=$'\033[0;32m' +readonly YELLOW=$'\033[1;33m' +readonly BLUE=$'\033[0;34m' +readonly CYAN=$'\033[0;36m' +readonly BOLD=$'\033[1m' +readonly DIM=$'\033[2m' +readonly NC=$'\033[0m' + +bar() { + printf '%b\n' "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +sub() { + printf '%b %s\n' "${DIM}" "$1${NC}" +} + +err() { + printf '%b %s\n' "${RED}" "$1${NC}" +} + +step_begin() { + local idx="$1" + local total="$2" + local name="$3" + local tags="$4" + printf '\n' + bar + printf '%b Step %s/%s %s\n' "${BOLD}" "$idx" "$total" "$name${NC}" + printf '%b 构建标签 (GO_BUILD_TAGS): %s%s\n' "${CYAN}" "$tags" "${NC}" + bar + printf '%b ⏳ 正在执行 make install …%s\n' "${YELLOW}" "${NC}" +} + +step_ok() { + local secs="$1" + printf '%b ✅ 本步编译成功%s' "${GREEN}" "${NC}" + printf '%b (耗时 %ss)%s\n' "${DIM}" "$secs" "${NC}" +} + +TOTAL_STEPS=4 +FAILED=0 + +run_one() { + local idx="$1" name="$2" tags="$3" + shift 3 + local start=$SECONDS + step_begin "$idx" "$TOTAL_STEPS" "$name" "$tags" + sub "命令: make install $*" + if make install "$@"; then + step_ok "$((SECONDS - start))" + else + err "❌ 本步失败: ${name}" + FAILED=1 + return 1 + fi +} + +printf '\n%b\n' "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" +printf '%b\n' "${BOLD}${BLUE}║ 🔍 DMS 多版本构建校验 ║${NC}" +printf '%b\n' "${BOLD}${BLUE}║ 依次验证 Makefile install 在四种标签组合下均可通过 ║${NC}" +printf '%b\n' "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" +sub "工作目录: ${ROOT}" + +# 1 社区版: dummyhead(EDITION 默认 ce) +run_one 1 "社区版 (CE)" "dummyhead" || true + +# 2 试用版: dummyhead + trial +if [[ "$FAILED" -eq 0 ]]; then + run_one 2 "试用版 (Trial)" "dummyhead,trial" EDITION=trial || true +fi + +# 3 企业版: dummyhead + enterprise +if [[ "$FAILED" -eq 0 ]]; then + run_one 3 "企业版 (EE)" "dummyhead,enterprise" EDITION=ee || true +fi + +# 4 DMS 企业版: dummyhead + enterprise + dms +if [[ "$FAILED" -eq 0 ]]; then + run_one 4 "DMS 企业版 (EE + DMS)" "dummyhead,enterprise,dms" EDITION=ee PRODUCT_CATEGORY=dms || true +fi + +printf '\n' +bar +if [[ "$FAILED" -eq 0 ]]; then + printf '%b🎉 全部 %s 个版本构建校验通过。%s\n' "${GREEN}${BOLD}" "$TOTAL_STEPS" "${NC}" + bar + printf '\n' + exit 0 +else + printf '%b💥 构建校验未全部通过,请根据上方日志修复后重试。%s\n' "${RED}${BOLD}" "${NC}" + bar + printf '\n' + exit 1 +fi