Coverage Summary for Class: DataGuardPlugin (com.kotlinorm.plugins)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
DataGuardPlugin |
90.9%
(10/11)
|
81.1%
(30/37)
|
90.9%
(30/33)
|
89%
(153/172)
|
DataGuardPlugin$OperationPolicy |
100%
(2/2)
|
75%
(3/4)
|
100%
(7/7)
|
97.9%
(47/48)
|
DataGuardPlugin$OperationPolicyBuilder |
83.3%
(5/6)
|
|
88.9%
(8/9)
|
96.2%
(76/79)
|
DataGuardPlugin$PolicyInfo |
100%
(4/4)
|
75%
(6/8)
|
100%
(10/10)
|
95.1%
(78/82)
|
DataGuardPlugin$ProtectionConfig |
100%
(1/1)
|
|
100%
(5/5)
|
100%
(70/70)
|
DataGuardPlugin$ProtectionConfigBuilder |
85.7%
(6/7)
|
|
94.1%
(16/17)
|
91.2%
(73/80)
|
DataGuardPlugin$WhenMappings |
|
Total |
90.3%
(28/31)
|
79.6%
(39/49)
|
93.8%
(76/81)
|
93.6%
(497/531)
|
/**
* Copyright 2022-2025 kronos-orm
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kotlinorm.plugins
import com.kotlinorm.beans.task.registerTaskEventPlugin
import com.kotlinorm.beans.task.unregisterTaskEventPlugin
import com.kotlinorm.database.SqlManager.getDBNameFrom
import com.kotlinorm.enums.KOperationType
import com.kotlinorm.interfaces.TaskEventPlugin
import com.kotlinorm.types.ActionTaskEvent
import com.kotlinorm.types.QueryTaskEvent
/**
* Plugin to block the whole table from being deleted, updated, or truncated.
*/
object DataGuardPlugin : TaskEventPlugin {
data class PolicyInfo(
var databaseName: String = "%",
var tableName: String? = null,
) {
/**
* 检查目标数据库和表是否匹配当前策略
* - databaseName为null表示匹配任意数据库
* - tableName为null表示匹配任意表
*/
fun matches(targetDatabase: String, targetTable: String?): Boolean {
fun isMatch(pattern: String?, target: String?) = when {
pattern == null -> true
target == null -> false
"%" in pattern -> pattern.toRegex().matches(target)
else -> pattern == target
}
return isMatch(databaseName, targetDatabase) && isMatch(tableName, targetTable)
}
private fun String.toRegex() = replace("%", ".*")
.let { Regex("^$it$") }
}
data class OperationPolicy(
val preventByDefault: Boolean = true,
val whitelist: Set<PolicyInfo> = emptySet(),
val blacklist: Set<PolicyInfo> = emptySet(),
) {
fun isAllowed(databaseName: String, tableName: String?): Boolean {
// 当tableName为null时始终允许
if (tableName == null) return true
return if (preventByDefault) {
// 默认阻止模式:只有白名单中的条目能通过
whitelist.any { it.matches(databaseName, tableName) }
} else {
// 默认允许模式:黑名单中的条目会被阻止
!blacklist.any { it.matches(databaseName, tableName) }
}
}
}
/**
* Configuration for the protection plugin.
*/
data class ProtectionConfig(
/**
* Config for delete without where operation
*/
val deleteAll: OperationPolicy = OperationPolicy(preventByDefault = true),
/**
* Config for update without where operation
*/
val updateAll: OperationPolicy = OperationPolicy(preventByDefault = true),
/**
* Config for truncate operation
*/
val truncate: OperationPolicy = OperationPolicy(preventByDefault = true),
/**
* Config for drop operation
*/
val drop: OperationPolicy = OperationPolicy(preventByDefault = true),
/**
* Config for alter operation
*/
val alter: OperationPolicy = OperationPolicy(preventByDefault = true)
)
private var loaded = false
var enabled
get() = loaded
set(value) {
if (value && !loaded) {
registerTaskEventPlugin(DataGuardPlugin)
loaded = true
}
if (!value && loaded) {
unregisterTaskEventPlugin(DataGuardPlugin)
loaded = false
}
}
private var pluginConfig = ProtectionConfig()
class OperationPolicyBuilder {
var preventByDefault: Boolean = true
val whitelist = mutableSetOf<PolicyInfo>()
val blacklist = mutableSetOf<PolicyInfo>()
fun denyAll() {
preventByDefault = true
}
fun allowAll() {
preventByDefault = false
}
fun allow(block: PolicyInfo.() -> Unit) {
whitelist.add(PolicyInfo().apply(block))
}
fun deny(block: PolicyInfo.() -> Unit) {
blacklist.add(PolicyInfo().apply(block))
}
fun build(): OperationPolicy {
return OperationPolicy(preventByDefault, whitelist.toSet(), blacklist.toSet())
}
}
class ProtectionConfigBuilder {
private val deleteAllBuilder = OperationPolicyBuilder()
private val updateAllBuilder = OperationPolicyBuilder()
private val truncateBuilder = OperationPolicyBuilder()
private val dropBuilder = OperationPolicyBuilder()
private val alterBuilder = OperationPolicyBuilder()
fun deleteAll(block: OperationPolicyBuilder.() -> Unit) {
deleteAllBuilder.apply(block)
}
fun updateAll(block: OperationPolicyBuilder.() -> Unit) {
updateAllBuilder.apply(block)
}
fun truncate(block: OperationPolicyBuilder.() -> Unit) {
truncateBuilder.apply(block)
}
fun drop(block: OperationPolicyBuilder.() -> Unit) {
dropBuilder.apply(block)
}
fun alter(block: OperationPolicyBuilder.() -> Unit) {
alterBuilder.apply(block)
}
fun build(): ProtectionConfig {
return ProtectionConfig(
deleteAll = deleteAllBuilder.build(),
updateAll = updateAllBuilder.build(),
truncate = truncateBuilder.build(),
drop = dropBuilder.build(),
alter = alterBuilder.build()
)
}
}
fun enable(config: (ProtectionConfigBuilder.() -> Unit)? = null) {
this.enabled = true
if (config != null) {
val builder = ProtectionConfigBuilder()
builder.apply(config)
pluginConfig = builder.build()
}
}
fun disable() {
this.enabled = false
}
override val doBeforeQuery: QueryTaskEvent? = null
override val doAfterQuery: QueryTaskEvent? = null
override val doBeforeAction: ActionTaskEvent = { task, wrapper ->
val dbName = getDBNameFrom(wrapper)
val tableName = task.actionInfo?.tableName
val whereClause = task.actionInfo?.whereClause
when (task.operationType) {
KOperationType.TRUNCATE -> {
if (!pluginConfig.truncate.isAllowed(dbName, tableName)) {
throw UnsupportedOperationException("Truncate operation is not allowed.")
}
}
KOperationType.DROP -> {
if (!pluginConfig.drop.isAllowed(dbName, tableName)) {
throw UnsupportedOperationException("Drop operation is not allowed.")
}
}
KOperationType.ALTER -> {
if (!pluginConfig.alter.isAllowed(dbName, tableName)) {
throw UnsupportedOperationException("Alter operation is not allowed.")
}
}
KOperationType.DELETE -> {
if (!pluginConfig.deleteAll.isAllowed(dbName, tableName) && whereClause.isNullOrBlank()) {
throw UnsupportedOperationException("Delete operation is not allowed.")
}
}
KOperationType.UPDATE -> {
if (!pluginConfig.updateAll.isAllowed(dbName, tableName) && whereClause.isNullOrBlank()) {
throw UnsupportedOperationException("Update operation is not allowed.")
}
}
else -> {}
}
}
override val doAfterAction: ActionTaskEvent? = null
}