Repository: casbin/casbin
Pull request #1436 (2024-08-27 20:21:36)

feat: support ** in globMatch
Fix: https://github.com/casbin/casbin/issues/1422

casbin-bot:  @tangyang9464 @JalinWang please review
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/casbin/casbin?pullRequest=1436) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/casbin/casbin?pullRequest=1436) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/casbin/casbin?pullRequest=1436) it.</sub>

Patch:
diff --git a/go.mod b/go.mod
index 1a58a08..38ae83a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,7 @@
 module github.com/casbin/casbin/v2
 
 require (
+	github.com/bmatcuk/doublestar/v4 v4.6.1
 	github.com/casbin/govaluate v1.2.0
 	github.com/golang/mock v1.4.4
 )
diff --git a/go.sum b/go.sum
index e20dbd0..aeb4094 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,9 @@
+github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
+github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/casbin/govaluate v1.2.0 h1:wXCXFmqyY+1RwiKfYo3jMKyrtZmOL3kHwaqDyCPOYak=
 github.com/casbin/govaluate v1.2.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
-github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
-github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
-github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
-github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/util/builtin_operators.go b/util/builtin_operators.go
index bab9b0b..9b8b40e 100644
--- a/util/builtin_operators.go
+++ b/util/builtin_operators.go
@@ -18,12 +18,13 @@ import (
 	"errors"
 	"fmt"
 	"net"
-	"path"
 	"regexp"
 	"strings"
 	"sync"
 	"time"
 
+	"github.com/bmatcuk/doublestar/v4"
+
 	"github.com/casbin/casbin/v2/rbac"
 
 	"github.com/casbin/govaluate"
@@ -370,7 +371,7 @@ func IPMatchFunc(args ...interface{}) (interface{}, error) {
 
 // GlobMatch determines whether key1 matches the pattern of key2 using glob pattern.
 func GlobMatch(key1 string, key2 string) (bool, error) {
-	return path.Match(key2, key1)
+	return doublestar.Match(key2, key1)
 }
 
 // GlobMatchFunc is the wrapper for GlobMatch.


Test patch:
diff --git a/util/builtin_operators_test.go b/util/builtin_operators_test.go
index 728bce7..ba0f463 100644
--- a/util/builtin_operators_test.go
+++ b/util/builtin_operators_test.go
@@ -569,6 +569,75 @@ func TestGlobMatch(t *testing.T) {
 	testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo", false)
 	testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo*", false)
 	testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/*", false)
+
+	testGlobMatch(t, "/foo", "**/foo", true)
+	testGlobMatch(t, "/foo", "**/foo**", true)
+	testGlobMatch(t, "/foo", "**/foo/**", true)
+	testGlobMatch(t, "/foo/bar", "**/foo", false)
+	testGlobMatch(t, "/foo/bar", "**/foo**", false)
+	testGlobMatch(t, "/foo/bar", "**/foo/**", true)
+	testGlobMatch(t, "/foobar", "**/foo", false)
+	testGlobMatch(t, "/foobar", "**/foo**", true)
+	testGlobMatch(t, "/foobar", "**/foo/**", false)
+
+	testGlobMatch(t, "/prefix/foo", "**/foo", true)
+	testGlobMatch(t, "/prefix/foo", "**/foo**", true)
+	testGlobMatch(t, "/prefix/foo", "**/foo/**", true)
+	testGlobMatch(t, "/prefix/foo/bar", "**/foo", false)
+	testGlobMatch(t, "/prefix/foo/bar", "**/foo**", false)
+	testGlobMatch(t, "/prefix/foo/bar", "**/foo/**", true)
+	testGlobMatch(t, "/prefix/foobar", "**/foo", false)
+	testGlobMatch(t, "/prefix/foobar", "**/foo**", true)
+	testGlobMatch(t, "/prefix/foobar", "**/foo/**", false)
+
+	testGlobMatch(t, "/prefix/subprefix/foo", "**/foo", true)
+	testGlobMatch(t, "/prefix/subprefix/foo", "**/foo**", true)
+	testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/**", true)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo**", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/**", true)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo", false)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo**", true)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/**", false)
+
+	testGlobMatch(t, "/foo", "*/foo**", true)
+	testGlobMatch(t, "/foo", "**/foo*", true)
+	testGlobMatch(t, "/foo", "*/foo/**", true)
+	testGlobMatch(t, "/foo", "**/foo/*", false)
+	testGlobMatch(t, "/foo/bar", "*/foo**", false)
+	testGlobMatch(t, "/foo/bar", "**/foo*", false)
+	testGlobMatch(t, "/foo/bar", "*/foo/**", true)
+	testGlobMatch(t, "/foo/bar", "**/foo/*", true)
+	testGlobMatch(t, "/foobar", "*/foo**", true)
+	testGlobMatch(t, "/foobar", "**/foo*", true)
+	testGlobMatch(t, "/foobar", "*/foo/**", false)
+	testGlobMatch(t, "/foobar", "**/foo/*", false)
+
+	testGlobMatch(t, "/prefix/foo", "*/foo**", false)
+	testGlobMatch(t, "/prefix/foo", "**/foo*", true)
+	testGlobMatch(t, "/prefix/foo", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/foo", "**/foo/*", false)
+	testGlobMatch(t, "/prefix/foo/bar", "*/foo**", false)
+	testGlobMatch(t, "/prefix/foo/bar", "**/foo*", false)
+	testGlobMatch(t, "/prefix/foo/bar", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/foo/bar", "**/foo/*", true)
+	testGlobMatch(t, "/prefix/foobar", "*/foo**", false)
+	testGlobMatch(t, "/prefix/foobar", "**/foo*", true)
+	testGlobMatch(t, "/prefix/foobar", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/foobar", "**/foo/*", false)
+
+	testGlobMatch(t, "/prefix/subprefix/foo", "*/foo**", false)
+	testGlobMatch(t, "/prefix/subprefix/foo", "**/foo*", true)
+	testGlobMatch(t, "/prefix/subprefix/foo", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/*", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo**", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo*", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/*", true)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo**", false)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo*", true)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/**", false)
+	testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/*", false)
 }
 
 func testTimeMatch(t *testing.T, startTime string, endTime string, res bool) {