Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 49 additions & 69 deletions adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ type CasbinRule struct {
tableName struct{} `pg:"_"`
ID string
Ptype string
V0 string
V1 string
V2 string
V3 string
V4 string
V5 string
V0 string `pg:",use_zero"`
V1 string `pg:",use_zero"`
V2 string `pg:",use_zero"`
V3 string `pg:",use_zero"`
V4 string `pg:",use_zero"`
V5 string `pg:",use_zero"`
}

type Filter struct {
Expand Down Expand Up @@ -151,6 +151,22 @@ func (a *Adapter) createTableifNotExists() error {
return nil
}

// getValues returns the V0-V5 values as a slice
func (r *CasbinRule) getValues() []string {
return []string{r.V0, r.V1, r.V2, r.V3, r.V4, r.V5}
}

// getLastNonEmptyIndex returns the index of the last non-empty value in the given slice
// Returns -1 if all values are empty
func getLastNonEmptyIndex(values []string) int {
for i := len(values) - 1; i >= 0; i-- {
if values[i] != "" {
return i
}
}
return -1
}

func (r *CasbinRule) String() string {
const prefixLine = ", "
var sb strings.Builder
Expand All @@ -162,29 +178,15 @@ func (r *CasbinRule) String() string {
)

sb.WriteString(r.Ptype)
if len(r.V0) > 0 {
sb.WriteString(prefixLine)
sb.WriteString(r.V0)
}
if len(r.V1) > 0 {
sb.WriteString(prefixLine)
sb.WriteString(r.V1)
}
if len(r.V2) > 0 {
sb.WriteString(prefixLine)
sb.WriteString(r.V2)
}
if len(r.V3) > 0 {
sb.WriteString(prefixLine)
sb.WriteString(r.V3)
}
if len(r.V4) > 0 {

values := r.getValues()
lastIndex := getLastNonEmptyIndex(values)

// Include all values up to and including the last non-empty one
// This preserves empty strings in the middle while trimming trailing empty strings
for i := 0; i <= lastIndex; i++ {
sb.WriteString(prefixLine)
sb.WriteString(r.V4)
}
if len(r.V5) > 0 {
sb.WriteString(prefixLine)
sb.WriteString(r.V5)
sb.WriteString(values[i])
}

return sb.String()
Expand Down Expand Up @@ -547,31 +549,17 @@ func (a *Adapter) UpdateFilteredPolicies(sec string, ptype string, newPolicies [

func (c *CasbinRule) queryString() (string, []interface{}) {
queryArgs := []interface{}{c.Ptype}

queryStr := "ptype = ?"
if c.V0 != "" {
queryStr += " and v0 = ?"
queryArgs = append(queryArgs, c.V0)
}
if c.V1 != "" {
queryStr += " and v1 = ?"
queryArgs = append(queryArgs, c.V1)
}
if c.V2 != "" {
queryStr += " and v2 = ?"
queryArgs = append(queryArgs, c.V2)
}
if c.V3 != "" {
queryStr += " and v3 = ?"
queryArgs = append(queryArgs, c.V3)
}
if c.V4 != "" {
queryStr += " and v4 = ?"
queryArgs = append(queryArgs, c.V4)
}
if c.V5 != "" {
queryStr += " and v5 = ?"
queryArgs = append(queryArgs, c.V5)

values := c.getValues()
lastIndex := getLastNonEmptyIndex(values)

// Include all fields up to and including the last non-empty one
// This ensures empty strings in the middle are matched explicitly
fields := []string{"v0", "v1", "v2", "v3", "v4", "v5"}
for i := 0; i <= lastIndex; i++ {
queryStr += " and " + fields[i] + " = ?"
queryArgs = append(queryArgs, values[i])
}

return queryStr, queryArgs
Expand All @@ -582,24 +570,16 @@ func (c *CasbinRule) toStringPolicy() []string {
if c.Ptype != "" {
policy = append(policy, c.Ptype)
}
if c.V0 != "" {
policy = append(policy, c.V0)
}
if c.V1 != "" {
policy = append(policy, c.V1)
}
if c.V2 != "" {
policy = append(policy, c.V2)
}
if c.V3 != "" {
policy = append(policy, c.V3)
}
if c.V4 != "" {
policy = append(policy, c.V4)
}
if c.V5 != "" {
policy = append(policy, c.V5)

values := c.getValues()
lastIndex := getLastNonEmptyIndex(values)

// Include all values up to and including the last non-empty one
// This preserves empty strings in the middle while trimming trailing empty strings
for i := 0; i <= lastIndex; i++ {
policy = append(policy, values[i])
}

return policy
}

Expand Down
126 changes: 126 additions & 0 deletions adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,132 @@ func (s *AdapterTestSuite) TestUpdateFilteredPolicies() {

s.assertPolicy(s.e.GetPolicy(), [][]string{{"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}, {"alice", "data2", "write"}, {"bob", "data1", "read"}})
}

func (s *AdapterTestSuite) TestEmptyStringSupport() {
// Test that empty strings in the middle of rules are properly stored and retrieved
// Note: Casbin trims trailing empty strings, so we focus on empty strings in the middle
var err error

// Add policies with empty strings in the middle
_, err = s.e.AddPolicy("alice", "", "read")
s.Require().NoError(err)

_, err = s.e.AddPolicy("bob", "", "write")
s.Require().NoError(err)

// Reload to ensure they were saved correctly
err = s.e.LoadPolicy()
s.Require().NoError(err)

// Check that all policies including empty strings are present
policies := s.e.GetPolicy()

// Should have original 4 policies plus 2 new ones with empty strings
s.Assert().Len(policies, 6)

// Verify the new policies with empty strings exist
hasAliceEmpty := false
hasBobEmpty := false

for _, p := range policies {
if len(p) == 3 && p[0] == "alice" && p[1] == "" && p[2] == "read" {
hasAliceEmpty = true
}
if len(p) == 3 && p[0] == "bob" && p[1] == "" && p[2] == "write" {
hasBobEmpty = true
}
}

s.Assert().True(hasAliceEmpty, "Policy with alice and empty string in middle not found")
s.Assert().True(hasBobEmpty, "Policy with bob and empty string in middle not found")

// Test removing a policy with empty string
_, err = s.e.RemovePolicy("alice", "", "read")
s.Require().NoError(err)

err = s.e.LoadPolicy()
s.Require().NoError(err)

policies = s.e.GetPolicy()
s.Assert().Len(policies, 5)

// Verify alice policy with empty string was removed
for _, p := range policies {
if len(p) == 3 && p[0] == "alice" && p[1] == "" && p[2] == "read" {
s.Fail("Policy with alice and empty string should have been removed")
}
}

// Test updating a policy with empty string
_, err = s.e.UpdatePolicy([]string{"bob", "", "write"}, []string{"bob", "", "read"})
s.Require().NoError(err)

err = s.e.LoadPolicy()
s.Require().NoError(err)

policies = s.e.GetPolicy()
hasBobEmptyRead := false
for _, p := range policies {
if len(p) == 3 && p[0] == "bob" && p[1] == "" && p[2] == "read" {
hasBobEmptyRead = true
}
// Should not have the old policy anymore
if len(p) == 3 && p[0] == "bob" && p[1] == "" && p[2] == "write" {
s.Fail("Old policy with bob and write should have been updated")
}
}
s.Assert().True(hasBobEmptyRead, "Updated policy with bob and read not found")
}

func (s *AdapterTestSuite) TestEmptyStringVsWildcard() {
// Test that empty strings in rules are different from wildcards in filters
var err error

// Add a policy with an empty string
_, err = s.e.AddPolicy("user1", "", "write")
s.Require().NoError(err)

// Add a policy with a non-empty value
_, err = s.e.AddPolicy("user1", "resource1", "write")
s.Require().NoError(err)

// Reload
err = s.e.LoadPolicy()
s.Require().NoError(err)

// Both should exist
policies := s.e.GetPolicy()
hasEmpty := false
hasNonEmpty := false

for _, p := range policies {
if len(p) >= 3 && p[0] == "user1" && p[2] == "write" {
if p[1] == "" {
hasEmpty = true
} else if p[1] == "resource1" {
hasNonEmpty = true
}
}
}

s.Assert().True(hasEmpty, "Policy with empty string should exist")
s.Assert().True(hasNonEmpty, "Policy with resource1 should exist")

// Remove using wildcard (empty string in filter) should remove both
_, err = s.e.RemoveFilteredPolicy(0, "user1", "", "write")
s.Require().NoError(err)

err = s.e.LoadPolicy()
s.Require().NoError(err)

policies = s.e.GetPolicy()
for _, p := range policies {
if len(p) >= 3 && p[0] == "user1" && p[2] == "write" {
s.Fail("Policies with user1 and write should have been removed by wildcard filter")
}
}
}

func TestAdapterTestSuite(t *testing.T) {
suite.Run(t, new(AdapterTestSuite))
}