93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
|
|
package middleware
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
errs "gateway/internal/library/errors"
|
||
|
|
"gateway/internal/library/errors/code"
|
||
|
|
logicmember "gateway/internal/logic/member"
|
||
|
|
domperm "gateway/internal/model/permission/domain/usecase"
|
||
|
|
"gateway/internal/response"
|
||
|
|
|
||
|
|
"github.com/zeromicro/go-zero/core/logx"
|
||
|
|
"github.com/zeromicro/go-zero/rest"
|
||
|
|
)
|
||
|
|
|
||
|
|
// CasbinRBACOptions tunes the enforcement middleware.
|
||
|
|
type CasbinRBACOptions struct {
|
||
|
|
// PlatformAdminRoleKey short-circuits enforcement when the actor's
|
||
|
|
// auth context flags them as platform admin (handled upstream); the
|
||
|
|
// value is the role key seeded for that role (e.g.
|
||
|
|
// "platform_super_admin"). Empty disables the bypass.
|
||
|
|
PlatformAdminRoleKey string
|
||
|
|
|
||
|
|
// AllowMissingActor lets unauthenticated requests through without
|
||
|
|
// enforcement. Set to true on routes that do their own auth (e.g.
|
||
|
|
// public catalog reads in dev mode).
|
||
|
|
AllowMissingActor bool
|
||
|
|
|
||
|
|
// SkipPaths is an exact-match allowlist (e.g. /api/v1/health). Useful
|
||
|
|
// for opting out specific routes when the middleware is mounted
|
||
|
|
// globally.
|
||
|
|
SkipPaths map[string]struct{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CasbinRBAC returns a go-zero middleware that calls
|
||
|
|
// rbac.Check(tenant, uid, path, method) and rejects with HTTP 403 when
|
||
|
|
// the result is deny.
|
||
|
|
//
|
||
|
|
// The middleware is intentionally NOT wired into routes.go yet — wiring
|
||
|
|
// happens once the platform admin role + audit log pipeline are in
|
||
|
|
// place (design §6.7, §8.2). To opt-in, append it to a route group's
|
||
|
|
// middleware chain in routes.go.
|
||
|
|
func CasbinRBAC(rbac domperm.RBACUseCase, opts CasbinRBACOptions) rest.Middleware {
|
||
|
|
skip := opts.SkipPaths
|
||
|
|
if skip == nil {
|
||
|
|
skip = map[string]struct{}{}
|
||
|
|
}
|
||
|
|
bld := errs.For(code.Permission)
|
||
|
|
|
||
|
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if rbac == nil {
|
||
|
|
next(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if _, ok := skip[r.URL.Path]; ok {
|
||
|
|
next(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
actor, err := logicmember.ActorFromContext(r.Context())
|
||
|
|
if err != nil {
|
||
|
|
if opts.AllowMissingActor {
|
||
|
|
next(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
response.Write(r.Context(), w, nil,
|
||
|
|
bld.AuthUnauthorized("missing actor for rbac check").WithCause(err))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
result, err := rbac.Check(r.Context(), &domperm.CheckRequest{
|
||
|
|
TenantID: actor.TenantID,
|
||
|
|
UID: actor.UID,
|
||
|
|
Path: r.URL.Path,
|
||
|
|
Method: r.Method,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
logx.WithContext(r.Context()).Errorf(
|
||
|
|
"casbin: enforce error tenant=%s uid=%s path=%s method=%s: %v",
|
||
|
|
actor.TenantID, actor.UID, r.URL.Path, r.Method, err)
|
||
|
|
response.Write(r.Context(), w, nil,
|
||
|
|
bld.SysInternal("casbin enforce failed").WithCause(err))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if !result.Allow {
|
||
|
|
response.Write(r.Context(), w, nil,
|
||
|
|
bld.AuthForbidden("rbac denied").WithCause(nil))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
next(w, r)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|