From acb55bb53ad73eabc661f2a18544d0432e6d1aa3 Mon Sep 17 00:00:00 2001 From: what Date: Wed, 22 Jul 2026 14:03:33 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84:=20ResScope=20=E6=94=B9?= =?UTF-8?q?=E6=88=90=20ResFlags=20=E4=BD=8D=E6=A0=87=E5=BF=97,=E8=A1=8C?= =?UTF-8?q?=E7=BA=A7=E8=BF=87=E6=BB=A4=E4=B8=8E=E5=AD=97=E6=AE=B5=E8=84=B1?= =?UTF-8?q?=E6=95=8F=E6=8B=86=E6=88=90=E7=8B=AC=E7=AB=8B=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResScope(Off/On/All 三选一)改成 ResFlags 位标志:ResRow/ResRowRelations 管行级权限过滤,ResMask/ResMaskRelations 管字段级脱敏,两个维度可以按位独立组合(*Relations 那两位只保留定义,还没接入判断逻辑)。WithRolesScope 改名 WithPermission(req.ResFlags),dataProcessor 里原来单一的 skipPermission 拆成 skipRowFilter/skipFieldMask 分别控制。 --- options.go | 16 ++++++++-------- resource.go | 23 ++++++++++++++++------- resx/res_interceptor.go | 13 +++++++------ resx/resource.go | 10 ++++++++-- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/options.go b/options.go index b2f7e6d..3cc0316 100755 --- a/options.go +++ b/options.go @@ -6,9 +6,9 @@ type ResOptions struct { Tx *db.TxDatabase Alias string Silent bool - // RolesScope 默认零值 ResScopeOff:不传 WithRolesScope,或显式传 ResScopeOff,都表示跳过 - // 权限过滤;只有显式传 ResScopeOn/ResScopeAll 才会按角色过滤。 - RolesScope ResScope + // Permission 默认零值 0:不传 WithPermission 时,行级过滤和字段脱敏都跳过;显式传 + // ResRow/ResMask(或组合 ResAll)才会开启对应的检查,两者相互独立。 + Permission ResFlags } type ResOption func(p *ResOptions) @@ -34,12 +34,12 @@ func WithSilent() ResOption { } } -// WithRolesScope 显式指定这次写/查操作的权限过滤范围。ResScopeOn/ResScopeAll 按角色过滤 -// (行级 WHERE 注入、字段级读脱敏、字段级写权限);ResScopeOff(或不调用这个 option)跳过 -// 权限过滤,变更通知、字段规范化、类型转换、默认值填充等不受影响。 -func WithRolesScope(s ResScope) ResOption { +// WithPermission 显式指定这次写/查操作要开启哪些权限检查。ResRow 开启行级过滤(WHERE 注入), +// ResMask 开启字段级脱敏(读脱敏、写权限),两者可以按位组合传,也可以只传一个;不调用这个 option +// 时两者都跳过。变更通知、字段规范化、类型转换、默认值填充等不受这个 option 影响。 +func WithPermission(f ResFlags) ResOption { return func(p *ResOptions) { - p.RolesScope = s + p.Permission = f } } diff --git a/resource.go b/resource.go index 7f53b4a..349e023 100755 --- a/resource.go +++ b/resource.go @@ -10,15 +10,24 @@ import ( ) type ResDataType string -type ResScope int + +// ResFlags 是一组可以按位组合的权限检查开关,零值表示什么检查都不做。行级过滤 +// (ResRow)和字段脱敏(ResMask)是两个独立维度,各自的 xxxRelations 位表示"连关联 +// 资源一起处理",目前只保留位定义,还没接入判断逻辑。 +type ResFlags int const ( - // 关闭权限过滤 - ResScopeOff ResScope = iota - // 开启权限过滤, 不包括关联资源 - ResScopeOn - // 开启权限过滤, 包括关联资源 - ResScopeAll + // ResRow 行级权限过滤:按角色过滤能看到/操作的行,只处理当前资源自己 + ResRow ResFlags = 1 << iota + // ResRowRelations 行级权限过滤扩展到关联资源(依附于 ResRow,单独设置无效果,尚未实现) + ResRowRelations + // ResMask 字段级脱敏:按 ResField.GetRoles() 脱敏无权限查看/写入的字段,只处理当前资源自己 + ResMask + // ResMaskRelations 字段级脱敏扩展到关联资源(依附于 ResMask,单独设置无效果,尚未实现) + ResMaskRelations + + // ResAll 常用组合:行级过滤 + 字段脱敏都开启(不含关联资源) + ResAll = ResRow | ResMask ) const ( diff --git a/resx/res_interceptor.go b/resx/res_interceptor.go index 1d520a8..27fa50b 100644 --- a/resx/res_interceptor.go +++ b/resx/res_interceptor.go @@ -42,8 +42,9 @@ type dataProcessor struct { ownsTx bool // tx 是否由本次写操作自动开启(而非调用方传入),决定 After 要不要 Commit/Rollback onChange ResChangeRowFunc // 非 nil 表示本次写操作需要抓取快照/收集变更内容,由 Before 阶段的 DataInterceptor 决定 rows []changeRow - silent bool // ResOptions.Silent:跳过变更通知,见 req.WithSilent 注释 - skipPermission bool // ResOptions.RolesScope 不是 ResScopeOn/ResScopeAll:跳过权限过滤/字段脱敏,见 req.WithRolesScope 注释 + silent bool // ResOptions.Silent:跳过变更通知,见 req.WithSilent 注释 + skipRowFilter bool // ResOptions.Permission 不含 ResRow:跳过行级权限过滤,见 req.WithPermission 注释 + skipFieldMask bool // ResOptions.Permission 不含 ResMask:跳过字段级脱敏/写权限,见 req.WithPermission 注释 } // UseTx 在 Before 之前调用:如果本次写操作会触发 onChange、且调用方没有显式传入事务,自动开一个 @@ -82,11 +83,11 @@ func (dp *dataProcessor) UseTx(dataset interface{}) (exec.QueryFactory, error) { } // applyIntercept 调用容器里注册的 DataInterceptor,返回角色权限过滤条件,并记录本次写完后要不要调用、调用谁。 -// skipPermission 时丢弃行级权限过滤条件;silent 时不设置 onChange(写完后不会触发变更通知)。两者 +// skipRowFilter 时丢弃行级权限过滤条件;silent 时不设置 onChange(写完后不会触发变更通知)。两者 // 相互独立,可以只生效一个。 func (dp *dataProcessor) applyIntercept(category ResEventType) (sub *db.SelectDataset, cond db.Expression) { sub, cond, onChange := dp.res.intercept(dp.u, category) - if dp.skipPermission { + if dp.skipRowFilter { sub, cond = nil, nil } if !dp.silent { @@ -408,7 +409,7 @@ func (dp *dataProcessor) normalizeSaveValue(row db.Record) error { } field, hasField := dp.res.GetField(k) - if hasField && !dp.skipPermission && !hasFieldAccess(field, dp.u) { + if hasField && !dp.skipFieldMask && !hasFieldAccess(field, dp.u) { delete(row, k) continue } @@ -489,7 +490,7 @@ func (dp *dataProcessor) beforeSelectDataset(sd *db.SelectDataset) error { // 真正解析 MaskField 标记的别名对应哪个资源、哪个字段(见 resolveLazyMask),解析失败说明 // 调用方标记的表名/字段名写错了,是编程错误,直接报错而不是静默跳过。 func (dp *dataProcessor) maskSelect(sd *db.SelectDataset) error { - if dp.res.IsVirtual() || dp.skipPermission { + if dp.res.IsVirtual() || dp.skipFieldMask { return nil } diff --git a/resx/resource.go b/resx/resource.go index 1174a83..f269e8c 100644 --- a/resx/resource.go +++ b/resx/resource.go @@ -325,8 +325,14 @@ func (this *resource) GetDBTable(u req.User, opts ...req.ResOption) (sd *db.Sele sd = op.Tx.From(this.GetTableExpr().As(alias)) } - skipPermission := op.RolesScope != req.ResScopeOn && op.RolesScope != req.ResScopeAll - sd.WithHook(&dataProcessor{res: this, u: u, tx: op.Tx, silent: op.Silent, skipPermission: skipPermission}) + sd.WithHook(&dataProcessor{ + res: this, + u: u, + tx: op.Tx, + silent: op.Silent, + skipRowFilter: op.Permission&req.ResRow == 0, + skipFieldMask: op.Permission&req.ResMask == 0, + }) return sd }