feat: Enable ABAC support in /api/enforce and /api/batch-enforce

This commit is contained in:
Yang Luo
2026-04-09 00:15:30 +08:00
parent 10daed237e
commit 91cdf56636
4 changed files with 70 additions and 12 deletions

View File

@@ -57,7 +57,9 @@ func (c *ApiController) Enforce() {
return
}
var request []string
// Accept both plain string arrays (["alice","data1","read"]) and mixed arrays
// with JSON objects ([{"DivisionGuid":"x"}, "resource", "read"]) for ABAC support.
var request []interface{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {
c.ResponseError(err.Error())
@@ -74,8 +76,8 @@ func (c *ApiController) Enforce() {
res := []bool{}
keyRes := []string{}
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
// Convert elements: JSON-object strings and maps become anonymous structs for ABAC.
interfaceRequest := util.InterfaceToEnforceArray(request)
enforceResult, err := enforcer.Enforce(interfaceRequest...)
if err != nil {
@@ -197,7 +199,8 @@ func (c *ApiController) BatchEnforce() {
return
}
var requests [][]string
// Accept both string arrays and mixed arrays with JSON objects for ABAC support.
var requests [][]interface{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
c.ResponseError(err.Error())
@@ -214,8 +217,8 @@ func (c *ApiController) BatchEnforce() {
res := [][]bool{}
keyRes := []string{}
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
// Convert elements: JSON-object strings and maps become anonymous structs for ABAC.
interfaceRequests := util.InterfaceToEnforceArray2d(requests)
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
if err != nil {

View File

@@ -357,26 +357,27 @@ func removePolicies(permission *Permission) error {
return err
}
func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
func Enforce(permission *Permission, request []interface{}, permissionIds ...string) (bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return false, err
}
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
// Convert each element: JSON-object strings and maps become anonymous structs
// so Casbin can evaluate ABAC rules with dot-notation (e.g. r.sub.DivisionGuid).
interfaceRequest := util.InterfaceToEnforceArray(request)
return enforcer.Enforce(interfaceRequest...)
}
func BatchEnforce(permission *Permission, requests [][]string, permissionIds ...string) ([]bool, error) {
func BatchEnforce(permission *Permission, requests [][]interface{}, permissionIds ...string) ([]bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return nil, err
}
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
// Convert each element in every row for ABAC support.
interfaceRequests := util.InterfaceToEnforceArray2d(requests)
return enforcer.BatchEnforce(interfaceRequests)
}

View File

@@ -67,3 +67,35 @@ func TryJsonToAnonymousStruct(j string) (interface{}, error) {
}
return i, nil
}
// InterfaceToEnforceValue converts a single request value for use in Casbin ABAC enforcement.
// - Strings that are valid JSON objects are converted to anonymous structs so Casbin can
// access their fields (e.g. r.sub.DivisionGuid).
// - Maps (map[string]interface{}) produced by direct JSON unmarshaling are re-marshaled and
// then converted to anonymous structs in the same way.
// - All other values are returned unchanged.
func InterfaceToEnforceValue(v interface{}) interface{} {
switch val := v.(type) {
case string:
jStruct, err := TryJsonToAnonymousStruct(val)
if err == nil {
return jStruct
}
return val
case map[string]interface{}:
// The value was already decoded as a JSON object; re-encode it so we
// can reuse TryJsonToAnonymousStruct to produce a named-field struct
// that Casbin can evaluate with dot-notation (r.sub.Field).
jsonBytes, err := json.Marshal(val)
if err != nil {
return val
}
jStruct, err := TryJsonToAnonymousStruct(string(jsonBytes))
if err == nil {
return jStruct
}
return val
default:
return v
}
}

View File

@@ -381,3 +381,25 @@ func StringToInterfaceArray2d(arrays [][]string) [][]interface{} {
}
return interfaceArrays
}
// InterfaceToEnforceArray converts a []interface{} request for use in Casbin ABAC enforcement.
// Each element is processed by InterfaceToEnforceValue: plain strings that are valid JSON
// objects and map values decoded directly from JSON are both converted to anonymous structs
// so Casbin can evaluate attribute-based rules with dot-notation (r.sub.Field).
func InterfaceToEnforceArray(array []interface{}) []interface{} {
result := make([]interface{}, len(array))
for i, elem := range array {
result[i] = InterfaceToEnforceValue(elem)
}
return result
}
// InterfaceToEnforceArray2d applies InterfaceToEnforceArray to every row in a
// two-dimensional slice, for use with Casbin BatchEnforce.
func InterfaceToEnforceArray2d(arrays [][]interface{}) [][]interface{} {
result := make([][]interface{}, len(arrays))
for i, arr := range arrays {
result[i] = InterfaceToEnforceArray(arr)
}
return result
}