187 lines
6.5 KiB
Go
187 lines
6.5 KiB
Go
|
|
package service
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
withmodel "with_you/internal/model"
|
|||
|
|
|
|||
|
|
"github.com/casbin/casbin/v3"
|
|||
|
|
casbinmodel "github.com/casbin/casbin/v3/model"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// newTestEnforcer 构造一个内存 Enforcer,加载与生产相同的 model.conf 语义(globMatch),
|
|||
|
|
// 并按 seedPermissions 的约定注入角色策略,便于在不依赖 DB 的情况下验证 matcher 行为。
|
|||
|
|
func newTestEnforcer(t *testing.T) *casbin.Enforcer {
|
|||
|
|
t.Helper()
|
|||
|
|
// 与 configs/casbin/model.conf 保持一致;用 globMatch 而非 keyMatch2。
|
|||
|
|
mText := `
|
|||
|
|
[request_definition]
|
|||
|
|
r = sub, obj, act
|
|||
|
|
|
|||
|
|
[policy_definition]
|
|||
|
|
p = sub, obj, act
|
|||
|
|
|
|||
|
|
[role_definition]
|
|||
|
|
g = _, _
|
|||
|
|
|
|||
|
|
[policy_effect]
|
|||
|
|
e = some(where (p.eft == allow))
|
|||
|
|
|
|||
|
|
[matchers]
|
|||
|
|
m = r.sub == p.sub && globMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
|
|||
|
|
`
|
|||
|
|
m, err := casbinmodel.NewModelFromString(mText)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("parse model: %v", err)
|
|||
|
|
}
|
|||
|
|
e, err := casbin.NewEnforcer(m)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("new enforcer: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 与 seedPermissions 一致:super_admin=admin.** 通配,admin 业务能力,
|
|||
|
|
// moderator 内容审核。admin.logs.export 仅 super_admin 拥有(admin 只有 admin.logs read)。
|
|||
|
|
policies := [][]string{
|
|||
|
|
{withmodel.RoleSuperAdmin, "admin/**", "*"},
|
|||
|
|
|
|||
|
|
{withmodel.RoleAdmin, "admin/users", "read"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/users/status", "write"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/users/devices", "read"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/posts", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/comments", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/groups", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/channels", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/dashboard", "read"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/reports", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/verifications", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/profile_audits", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/materials", "*"},
|
|||
|
|
{withmodel.RoleAdmin, "admin/logs", "read"},
|
|||
|
|
|
|||
|
|
{withmodel.RoleModerator, "admin/posts", "read"},
|
|||
|
|
{withmodel.RoleModerator, "admin/posts", "write"},
|
|||
|
|
{withmodel.RoleModerator, "admin/comments", "read"},
|
|||
|
|
{withmodel.RoleModerator, "admin/comments", "write"},
|
|||
|
|
{withmodel.RoleModerator, "admin/reports", "read"},
|
|||
|
|
{withmodel.RoleModerator, "admin/reports", "write"},
|
|||
|
|
}
|
|||
|
|
for _, p := range policies {
|
|||
|
|
if _, err := e.AddPolicy(p[0], p[1], p[2]); err != nil {
|
|||
|
|
t.Fatalf("add policy %v: %v", p, err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mustAllow / mustDeny 简化断言。
|
|||
|
|
func mustAllow(t *testing.T, e *casbin.Enforcer, sub, obj, act string) {
|
|||
|
|
t.Helper()
|
|||
|
|
ok, err := e.Enforce(sub, obj, act)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("Enforce(%s,%s,%s) err: %v", sub, obj, act, err)
|
|||
|
|
}
|
|||
|
|
if !ok {
|
|||
|
|
t.Errorf("Enforce(%s,%s,%s) = false, want true", sub, obj, act)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
func mustDeny(t *testing.T, e *casbin.Enforcer, sub, obj, act string) {
|
|||
|
|
t.Helper()
|
|||
|
|
ok, err := e.Enforce(sub, obj, act)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("Enforce(%s,%s,%s) err: %v", sub, obj, act, err)
|
|||
|
|
}
|
|||
|
|
if ok {
|
|||
|
|
t.Errorf("Enforce(%s,%s,%s) = true, want false", sub, obj, act)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestSuperAdminWildcard 通配多级资源(globMatch ** 跨 '.')。
|
|||
|
|
func TestSuperAdminWildcard(t *testing.T) {
|
|||
|
|
e := newTestEnforcer(t)
|
|||
|
|
mustAllow(t, e, withmodel.RoleSuperAdmin, "admin/users/roles/write", "write")
|
|||
|
|
mustAllow(t, e, withmodel.RoleSuperAdmin, "admin/logs/export", "read")
|
|||
|
|
mustAllow(t, e, withmodel.RoleSuperAdmin, "admin/dashboard", "read")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestAdminBusinessCapabilities admin 拥有的业务能力放行。
|
|||
|
|
func TestAdminBusinessCapabilities(t *testing.T) {
|
|||
|
|
e := newTestEnforcer(t)
|
|||
|
|
mustAllow(t, e, withmodel.RoleAdmin, "admin/posts", "write")
|
|||
|
|
mustAllow(t, e, withmodel.RoleAdmin, "admin/posts", "read")
|
|||
|
|
mustAllow(t, e, withmodel.RoleAdmin, "admin/users", "read")
|
|||
|
|
mustAllow(t, e, withmodel.RoleAdmin, "admin/logs", "read")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestAdminCannotReachSuperAdminOnlyResources admin 不能触及 super_admin 独占资源。
|
|||
|
|
// - admin.logs.export 仅 super_admin 拥有,admin 应被拒。
|
|||
|
|
// - admin.users.roles.write 仅 super_admin 拥有。
|
|||
|
|
// - admin.roles.* 仅 super_admin 拥有。
|
|||
|
|
func TestAdminCannotReachSuperAdminOnlyResources(t *testing.T) {
|
|||
|
|
e := newTestEnforcer(t)
|
|||
|
|
mustDeny(t, e, withmodel.RoleAdmin, "admin/logs/export", "read")
|
|||
|
|
mustDeny(t, e, withmodel.RoleAdmin, "admin/users/roles", "write")
|
|||
|
|
mustDeny(t, e, withmodel.RoleAdmin, "admin/roles", "read")
|
|||
|
|
// admin.users.status.write 是 admin 自己的能力,确认不受影响。
|
|||
|
|
mustAllow(t, e, withmodel.RoleAdmin, "admin/users/status", "write")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestModeratorContentModeration moderator 仅内容审核。
|
|||
|
|
func TestModeratorContentModeration(t *testing.T) {
|
|||
|
|
e := newTestEnforcer(t)
|
|||
|
|
mustAllow(t, e, withmodel.RoleModerator, "admin/posts", "read")
|
|||
|
|
mustAllow(t, e, withmodel.RoleModerator, "admin/comments", "write")
|
|||
|
|
mustAllow(t, e, withmodel.RoleModerator, "admin/reports", "write")
|
|||
|
|
// moderator 不应触及用户管理。
|
|||
|
|
mustDeny(t, e, withmodel.RoleModerator, "admin/users", "read")
|
|||
|
|
mustDeny(t, e, withmodel.RoleModerator, "admin/logs", "read")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestWildcardDoesNotCrossBoundaryWithSingleStar 校验 admin/* 在 globMatch 下不跨 '/'。
|
|||
|
|
// 此用例用于防止未来有人误把 super_admin 通配写回 admin/*(单层),导致 super_admin 实际拿不到
|
|||
|
|
// admin/users/roles/write 这类多级资源的能力(应使用 admin/**)。
|
|||
|
|
//
|
|||
|
|
// 注意:本测试构造独立的 Enforcer,仅注入 admin/* 单层策略,避免 newTestEnforcer 的
|
|||
|
|
// admin/** 策略干扰断言。
|
|||
|
|
func TestWildcardDoesNotCrossBoundaryWithSingleStar(t *testing.T) {
|
|||
|
|
mText := `
|
|||
|
|
[request_definition]
|
|||
|
|
r = sub, obj, act
|
|||
|
|
|
|||
|
|
[policy_definition]
|
|||
|
|
p = sub, obj, act
|
|||
|
|
|
|||
|
|
[role_definition]
|
|||
|
|
g = _, _
|
|||
|
|
|
|||
|
|
[policy_effect]
|
|||
|
|
e = some(where (p.eft == allow))
|
|||
|
|
|
|||
|
|
[matchers]
|
|||
|
|
m = r.sub == p.sub && globMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
|
|||
|
|
`
|
|||
|
|
m, err := casbinmodel.NewModelFromString(mText)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("parse model: %v", err)
|
|||
|
|
}
|
|||
|
|
e, err := casbin.NewEnforcer(m)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("new enforcer: %v", err)
|
|||
|
|
}
|
|||
|
|
if _, err := e.AddPolicy(withmodel.RoleSuperAdmin, "admin/*", "*"); err != nil {
|
|||
|
|
t.Fatalf("add policy: %v", err)
|
|||
|
|
}
|
|||
|
|
// 单层 * 不跨 '/':admin/* 仅命中 admin/<segment>,不能命中 admin/users/roles/write。
|
|||
|
|
mustDeny(t, e, withmodel.RoleSuperAdmin, "admin/users/roles/write", "write")
|
|||
|
|
mustAllow(t, e, withmodel.RoleSuperAdmin, "admin/posts", "write")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestEnforceForUser_NoRolesNoPanic 用户无角色时 EnforceForUser 走默认 user 角色。
|
|||
|
|
func TestEnforceForUser_NoRolesNoPanic(t *testing.T) {
|
|||
|
|
e := newTestEnforcer(t)
|
|||
|
|
// user 角色无任何 admin 策略,应全部拒绝。
|
|||
|
|
mustDeny(t, e, "user", "admin/posts", "read")
|
|||
|
|
// 防止 lint 抱怨未使用 context。
|
|||
|
|
_ = context.Background()
|
|||
|
|
}
|