Skip to content

Commit bb1e417

Browse files
authored
feat: support "ABAC with policy rule" model (#15)
* feat: support dynamic struct creation for parameters * fix: replace deprecated strings.Title with cases.Title * style: add periods to comments to fix godot linter errors
1 parent b946552 commit bb1e417

File tree

6 files changed

+103
-70
lines changed

6 files changed

+103
-70
lines changed

cmd/enforce.go

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,107 +16,120 @@ package cmd
1616

1717
import (
1818
"encoding/json"
19+
"reflect"
20+
"regexp"
21+
"strconv"
1922

2023
"github.com/casbin/casbin/v2"
2124
"github.com/spf13/cobra"
25+
"golang.org/x/text/cases"
26+
"golang.org/x/text/language"
2227
)
2328

2429
type ResponseBody struct {
2530
Allow bool `json:"allow"`
2631
Explain []string `json:"explain"`
2732
}
2833

29-
// enforceExCmd represents the enforceEx command.
30-
var enforceExCmd = &cobra.Command{
31-
Use: "enforceEx",
32-
Short: "Test if a 'subject' can access a 'object' with a given 'action' based on the policy",
33-
Long: `Test if a 'subject' can access a 'object' with a given 'action' based on the policy`,
34-
Run: func(cmd *cobra.Command, args []string) {
35-
modelPath, _ := cmd.Flags().GetString("model")
36-
policyPath, _ := cmd.Flags().GetString("policy")
37-
38-
e, err := casbin.NewEnforcer(modelPath, policyPath)
39-
if err != nil {
40-
panic(err)
41-
}
34+
// Function to handle enforcement results.
35+
func handleEnforceResult(cmd *cobra.Command, res bool, explain []string, err error) {
36+
if err != nil {
37+
cmd.PrintErrf("Error during enforcement: %v\n", err)
38+
return
39+
}
40+
41+
response := ResponseBody{
42+
Allow: res,
43+
Explain: explain,
44+
}
45+
46+
encoder := json.NewEncoder(cmd.OutOrStdout())
47+
encoder.SetEscapeHTML(false)
48+
encoder.Encode(response)
49+
}
4250

43-
params := make([]interface{}, len(args))
44-
for i, v := range args {
45-
params[i] = v
51+
// Function to parse parameters and execute policy check.
52+
func executeEnforce(cmd *cobra.Command, args []string, isEnforceEx bool) {
53+
modelPath, _ := cmd.Flags().GetString("model")
54+
policyPath, _ := cmd.Flags().GetString("policy")
55+
56+
e, err := casbin.NewEnforcer(modelPath, policyPath)
57+
if err != nil {
58+
panic(err)
59+
}
60+
61+
// Define regex pattern to match format like {field: value}.
62+
paramRegex := regexp.MustCompile(`{\s*"?(\w+)"?\s*:\s*(\d+)\s*}`)
63+
64+
params := make([]interface{}, len(args))
65+
for i, v := range args {
66+
// Using regex pattern to match parameters.
67+
if matches := paramRegex.FindStringSubmatch(v); len(matches) == 3 {
68+
fieldName := matches[1]
69+
valueStr := matches[2]
70+
71+
// Convert value to integer.
72+
if val, err := strconv.Atoi(valueStr); err == nil {
73+
// Dynamically create struct type.
74+
caser := cases.Title(language.English)
75+
structType := reflect.StructOf([]reflect.StructField{
76+
{
77+
Name: caser.String(fieldName),
78+
Type: reflect.TypeOf(0),
79+
},
80+
})
81+
82+
// Create struct instance and set value.
83+
structValue := reflect.New(structType).Elem()
84+
structValue.Field(0).SetInt(int64(val))
85+
86+
params[i] = structValue.Interface()
87+
continue
88+
}
4689
}
90+
params[i] = v
91+
}
4792

93+
if isEnforceEx {
4894
res, explain, err := e.EnforceEx(params...)
49-
if err != nil {
50-
cmd.PrintErrf("Error during enforcement: %v\n", err)
51-
return
52-
}
53-
54-
response := ResponseBody{
55-
Allow: res,
56-
Explain: explain,
57-
}
58-
59-
jsonResponse, err := json.Marshal(response)
60-
if err != nil {
61-
cmd.PrintErrf("Error marshaling JSON: %v\n", err)
62-
return
63-
}
64-
65-
cmd.Println(string(jsonResponse))
66-
},
95+
handleEnforceResult(cmd, res, explain, err)
96+
} else {
97+
res, err := e.Enforce(params...)
98+
handleEnforceResult(cmd, res, []string{}, err)
99+
}
67100
}
68101

69102
// enforceCmd represents the enforce command.
70103
var enforceCmd = &cobra.Command{
71104
Use: "enforce",
72-
Short: "Test if a 'subject' can access a 'object' with a given 'action' based on the policy",
73-
Long: `Test if a 'subject' can access a 'object' with a given 'action' based on the policy`,
105+
Short: "Test if a 'subject' can access a 'object' with a given 'action' based on the policy.",
106+
Long: `Test if a 'subject' can access a 'object' with a given 'action' based on the policy.`,
74107
Run: func(cmd *cobra.Command, args []string) {
75-
modelPath, _ := cmd.Flags().GetString("model")
76-
policyPath, _ := cmd.Flags().GetString("policy")
77-
78-
e, err := casbin.NewEnforcer(modelPath, policyPath)
79-
if err != nil {
80-
panic(err)
81-
}
82-
83-
params := make([]interface{}, len(args))
84-
for i, v := range args {
85-
params[i] = v
86-
}
87-
88-
res, err := e.Enforce(params...)
89-
if err != nil {
90-
cmd.PrintErrf("Error during enforcement: %v\n", err)
91-
return
92-
}
93-
94-
response := ResponseBody{
95-
Allow: res,
96-
Explain: []string{},
97-
}
98-
99-
jsonResponse, err := json.Marshal(response)
100-
if err != nil {
101-
cmd.PrintErrf("Error marshaling response: %v\n", err)
102-
return
103-
}
108+
executeEnforce(cmd, args, false)
109+
},
110+
}
104111

105-
cmd.Println(string(jsonResponse))
112+
// enforceExCmd represents the enforceEx command.
113+
var enforceExCmd = &cobra.Command{
114+
Use: "enforceEx",
115+
Short: "Test if a 'subject' can access a 'object' with a given 'action' based on the policy.",
116+
Long: `Test if a 'subject' can access a 'object' with a given 'action' based on the policy.`,
117+
Run: func(cmd *cobra.Command, args []string) {
118+
executeEnforce(cmd, args, true)
106119
},
107120
}
108121

109122
func init() {
110123
rootCmd.AddCommand(enforceExCmd)
111124
rootCmd.AddCommand(enforceCmd)
112125

113-
enforceExCmd.Flags().StringP("model", "m", "", "Path to the model file")
126+
enforceExCmd.Flags().StringP("model", "m", "", "Path to the model file.")
114127
_ = enforceExCmd.MarkFlagRequired("model")
115-
enforceExCmd.Flags().StringP("policy", "p", "", "Path to the policy file")
128+
enforceExCmd.Flags().StringP("policy", "p", "", "Path to the policy file.")
116129
_ = enforceExCmd.MarkFlagRequired("policy")
117130

118-
enforceCmd.Flags().StringP("model", "m", "", "Path to the model file")
131+
enforceCmd.Flags().StringP("model", "m", "", "Path to the model file.")
119132
_ = enforceCmd.MarkFlagRequired("model")
120-
enforceCmd.Flags().StringP("policy", "p", "", "Path to the policy file")
133+
enforceCmd.Flags().StringP("policy", "p", "", "Path to the policy file.")
121134
_ = enforceCmd.MarkFlagRequired("policy")
122135
}

cmd/enforce_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@ func Test_enforceExCmd(t *testing.T) {
4242

4343
domainArgs := []string{"enforceEx", "-m", "../test/rbac_with_domains_model.conf", "-p", "../test/rbac_with_domains_policy.csv"}
4444
assertExecuteCommand(t, rootCmd, "{\"allow\":true,\"explain\":[\"admin\",\"domain1\",\"data1\",\"read\"]}\n", append(domainArgs, "alice", "domain1", "data1", "read")...)
45+
46+
// Test ABAC rule
47+
abacArgs := []string{"enforceEx", "-m", "../test/abac_rule_model.conf", "-p", "../test/abac_rule_policy.csv"}
48+
assertExecuteCommand(t, rootCmd, "{\"allow\":true,\"explain\":[\"r.sub.Age > 18\",\"/data1\",\"read\"]}\n", append(abacArgs, "{\"Age\":30}", "/data1", "read")...)
49+
assertExecuteCommand(t, rootCmd, "{\"allow\":false,\"explain\":[]}\n", append(abacArgs, "{\"Age\":15}", "/data1", "read")...)
4550
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.19
55
require (
66
github.com/casbin/casbin/v2 v2.97.0
77
github.com/spf13/cobra v1.8.1
8+
golang.org/x/text v0.3.0
89
)
910

1011
require (

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
2222
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
2323
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2424
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
25+
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
2526
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2627
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
2728
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

test/abac_rule_model.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[request_definition]
2+
r = sub, obj, act
3+
4+
[policy_definition]
5+
p = sub_rule, obj, act
6+
7+
[policy_effect]
8+
e = some(where (p.eft == allow))
9+
10+
[matchers]
11+
m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act

test/abac_rule_policy.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
p, r.sub.Age > 18, /data1, read
2+
p, r.sub.Age < 60, /data2, write

0 commit comments

Comments
 (0)