Skip to content

Commit 90b47cf

Browse files
authored
feat: Added CachedEnforcer and tests for it (#62)
Signed-off-by: Rushikesh Tote <rushi.tote@gmail.com>
1 parent c243902 commit 90b47cf

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

src/main/CachedEnforcer.lua

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--Copyright 2021 The casbin Authors. All Rights Reserved.
2+
--
3+
--Licensed under the Apache License, Version 2.0 (the "License");
4+
--you may not use this file except in compliance with the License.
5+
--You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
--Unless required by applicable law or agreed to in writing, software
10+
--distributed under the License is distributed on an "AS IS" BASIS,
11+
--WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
--See the License for the specific language governing permissions and
13+
--limitations under the License.
14+
15+
require("src.main.Enforcer")
16+
17+
-- CachedEnforcer wraps Enforcer and provides decision cache
18+
CachedEnforcer = {}
19+
setmetatable(CachedEnforcer, Enforcer)
20+
21+
-- Creates a cached enforcer via file or DB.
22+
function CachedEnforcer:new(model, adapter)
23+
local e = Enforcer:new(model, adapter)
24+
self.__index = self
25+
setmetatable(e, self)
26+
e.cacheEnabled = true
27+
e.m = {}
28+
return e
29+
end
30+
31+
-- enableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.
32+
function CachedEnforcer:enableCache(enabled)
33+
if enabled then
34+
self.cacheEnabled = true
35+
else
36+
self.cacheEnabled = false
37+
end
38+
end
39+
40+
-- enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
41+
-- if rvals is not string , ingore the cache
42+
function CachedEnforcer:enforce(...)
43+
if not self.cacheEnabled then
44+
return Enforcer.enforce(self, ...)
45+
end
46+
47+
local rvals = {...}
48+
local key = ""
49+
for _, rval in pairs(rvals) do
50+
if type(rval) == "string" then
51+
key = key .. rval .. "$$"
52+
else
53+
return Enforcer.enforce(self, ...)
54+
end
55+
end
56+
57+
local res, ok = self:getCachedResult(key)
58+
if ok then
59+
return res
60+
end
61+
62+
res = Enforcer.enforce(self, ...)
63+
64+
self:setCachedResult(key, res)
65+
return res
66+
end
67+
68+
function CachedEnforcer:getCachedResult(key)
69+
if self.m[key] ~= nil then
70+
return self.m[key], true
71+
end
72+
73+
return nil, false
74+
end
75+
76+
function CachedEnforcer:setCachedResult(key, res)
77+
self.m[key] = res
78+
end
79+
80+
function CachedEnforcer:invalidateCache()
81+
self.m = {}
82+
end

src/main/Enforcer.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require("src.main.ManagementEnforcer")
1717
-- Enforcer = ManagementEnforcer + RBAC API.
1818
Enforcer = {}
1919
setmetatable(Enforcer, ManagementEnforcer)
20+
Enforcer.__index = Enforcer
2021

2122
-- GetRolesForUser gets the roles that a user has.
2223
function Enforcer:GetRolesForUser(name, ...)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--Copyright 2021 The casbin Authors. All Rights Reserved.
2+
--
3+
--Licensed under the Apache License, Version 2.0 (the "License");
4+
--you may not use this file except in compliance with the License.
5+
--You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
--Unless required by applicable law or agreed to in writing, software
10+
--distributed under the License is distributed on an "AS IS" BASIS,
11+
--WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
--See the License for the specific language governing permissions and
13+
--limitations under the License.
14+
15+
local cached_enforcer_module = require("src.main.CachedEnforcer")
16+
local path = os.getenv("PWD") or io.popen("cd"):read()
17+
18+
describe("Cached Enforcer tests", function ()
19+
it("Test Cache", function ()
20+
local model = path .. "/examples/basic_model.conf"
21+
local policy = path .. "/examples/basic_policy.csv"
22+
23+
local e = CachedEnforcer:new(model, policy)
24+
-- The cache is enabled by default for a new CachedEnforcer.
25+
26+
assert.is.True(e:enforce("alice", "data1", "read"))
27+
assert.is.False(e:enforce("alice", "data1", "write"))
28+
assert.is.False(e:enforce("alice", "data2", "read"))
29+
assert.is.False(e:enforce("alice", "data2", "write"))
30+
31+
-- The cache is enabled, so even if we remove a policy rule, the decision
32+
-- for ("alice", "data1", "read") will still be true, as it uses the cached result.
33+
e:RemovePolicy("alice", "data1", "read")
34+
35+
assert.is.True(e:enforce("alice", "data1", "read"))
36+
assert.is.False(e:enforce("alice", "data1", "write"))
37+
assert.is.False(e:enforce("alice", "data2", "read"))
38+
assert.is.False(e:enforce("alice", "data2", "write"))
39+
40+
-- Now we invalidate the cache, then all first-coming Enforce() has to be evaluated in real-time.
41+
-- The decision for ("alice", "data1", "read") will be false now.
42+
e:invalidateCache()
43+
44+
assert.is.False(e:enforce("alice", "data1", "read"))
45+
assert.is.False(e:enforce("alice", "data1", "write"))
46+
assert.is.False(e:enforce("alice", "data2", "read"))
47+
assert.is.False(e:enforce("alice", "data2", "write"))
48+
49+
e:AddPolicy("alice", "data1", "read")
50+
51+
-- Disabling cache skips the cache data and generates result from Enforcer
52+
e:enableCache(false)
53+
54+
assert.is.True(e:enforce("alice", "data1", "read"))
55+
assert.is.False(e:enforce("alice", "data1", "write"))
56+
assert.is.False(e:enforce("alice", "data2", "read"))
57+
assert.is.False(e:enforce("alice", "data2", "write"))
58+
end)
59+
end)

0 commit comments

Comments
 (0)