Skip to content

Commit

Permalink
feat: keyMatch2 implementation (casbin#11)
Browse files Browse the repository at this point in the history
* feat: keyMatch2 implementation

* test: adding tests for keyMatch matcher function

* refactor: update files + key_match_2 naming

* refactor: key_match2? naming + documentation updated

* test: adding more tests for key_match2?
  • Loading branch information
Zat42 authored Mar 18, 2022
1 parent daafa79 commit c68edd2
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 2 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,16 @@ end
```

## TODO

### Global

Implement all [matchers' functions](https://casbin.org/docs/en/function):
- [x] regexMatch
- [ ] keyMatch
- [ ] keyGet
- [x] KeyMatch2
- [ ] keyGet2
- [ ] keyMatch3
- [ ] keyMatch4
- [ ] ipMatch
- [ ] globMatch
39 changes: 38 additions & 1 deletion lib/acx/enforcer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -437,13 +437,50 @@ defmodule Acx.Enforcer do
end
end

@doc """
Returns `true` if `key1` matches the pattern of `key2`.
Returns `false` otherwise.
`key_match2?/2` can handle three types of path / patterns :
URL path like `/alice_data/resource1`.
`:` pattern like `/alice_data/:resource`.
`*` pattern like `/alice_data/*`.
## Parameters
- `key1` should be a URL path.
- `key2` can be a URL path, a `:` pattern or a `*` pattern.
## Examples
iex> Enforcer.key_match2?("alice_data/resource1", "alice_data/*")
true
iex> Enforcer.key_match2?("alice_data/resource1", "alice_data/:resource")
true
"""
@spec key_match2?(String.t(), String.t()) :: boolean()
def key_match2?(key1, key2) do
key2 = String.replace(key2, "/*", "/.*")

with {:ok, r1} <- Regex.compile(":[^/]+"),
match <- Regex.replace(r1, key2, "[^/]+"),
{:ok, r2} <- Regex.compile("^" <> match <> "$") do
Regex.match?(r2, key1)
else
_ -> false
end
end

#
# Helpers
#

defp init_env do
%{
regex_match?: &regex_match?/2
regexMatch: &regex_match?/2,
keyMatch2: &key_match2?/2
}
end

Expand Down
2 changes: 1 addition & 1 deletion test/data/acl_restful.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ p = sub, obj, act
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && regex_match?(r.obj, p.obj) && regex_match?(r.act, p.act)
m = r.sub == p.sub && regexMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
11 changes: 11 additions & 0 deletions test/data/keymatch2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && r.act == p.act
2 changes: 2 additions & 0 deletions test/data/keymatch2.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
p, alice, /alice_data/:resource, GET
p, alice, /alice_data2/:id/using/:resId, GET
74 changes: 74 additions & 0 deletions test/enforcer/keymatch2_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
defmodule Acx.Enforcer.KeyMatch2Test do
use ExUnit.Case, async: true
alias Acx.Enforcer

@cfile "../data/keymatch2.conf" |> Path.expand(__DIR__)
@pfile "../data/keymatch2.csv" |> Path.expand(__DIR__)

setup do
{:ok, e} = Enforcer.init(@cfile)

e =
e
|> Enforcer.load_policies!(@pfile)
|> Enforcer.load_mapping_policies!(@pfile)

{:ok, e: e}
end

describe "allow?/2" do
@test_cases [
{["alice", "/alice_data/1", "GET"], true},
{["alice", "/alice_data2/1/using/2", "GET"], true},
{["alice", "/alice_data2/1/using/2", "POST"], false},
{["alice", "/alice_data2/1/using/2/admin/", "GET"], false},
{["alice", "/admin/alice_data2/1/using/2", "GET"], false},
{["bob", "/admin/alice_data2/1/using/2", "GET"], false}
]

Enum.each(@test_cases, fn {req, res} ->
test "response `#{res}` for request #{inspect(req)}", %{e: e} do
assert e |> Enforcer.allow?(unquote(req)) === unquote(res)
end
end)
end

describe "key_match2?/2" do
@test_cases [
{"/foo", "/foo", true},
{"/foo", "/foo*", true},
{"/foo", "/foo/*", false},
{"/foo/bar", "/foo", false},
{"/foo/bar", "/foo*", false},
{"/foo/bar", "/foo/*", true},
{"/foobar", "/foo", false},
{"/foobar", "/foo*", false},
{"/foobar", "/foo/*", false},

{"/", "/:resource", false},
{"/resource1", "/:resource", true},
{"/myid", "/:id/using/:resId", false},
{"/myid/using/myresid", "/:id/using/:resId", true},

{"/proxy/myid", "/proxy/:id/*", false},
{"/proxy/myid/", "/proxy/:id/*", true},
{"/proxy/myid/res", "/proxy/:id/*", true},
{"/proxy/myid/res/res2", "/proxy/:id/*", true},
{"/proxy/myid/res/res2/res3", "/proxy/:id/*", true},
{"/proxy/", "/proxy/:id/*", false},

{"/alice", "/:id", true},
{"/alice/all", "/:id/all", true},
{"/alice", "/:id/all", false},
{"/alice/all", "/:id", false},

{"/alice/all", "/:/all", false}
]

Enum.each(@test_cases, fn {key1, key2, res} ->
test "response `#{res}` for combination `#{key1}` `#{key2}`" do
assert Enforcer.key_match2?(unquote(key1), unquote(key2)) === unquote(res)
end
end)
end
end

0 comments on commit c68edd2

Please sign in to comment.