Skip to content

Commit

Permalink
feat: Added tests for Python Bindings (#133)
Browse files Browse the repository at this point in the history
* feat: Added CI for python bindings

Signed-off-by: Yash Pandey (YP) <yash.btech.cs19@iiitranchi.ac.in>

* fix: Pybind CI

Signed-off-by: Yash Pandey (YP) <yash.btech.cs19@iiitranchi.ac.in>
  • Loading branch information
EmperorYP7 authored Aug 4, 2021
1 parent 6d8baae commit 3332324
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 3 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/python_binding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2021 The casbin Authors. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Python Bindings Test

on: [push, pull_request]

jobs:
benchmark:
name: Python Bindings Test
runs-on: macos-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v2
- name: Configuring CMake files
id: building-files
run: |
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE:STRING=Release
- name: Building library
id: building-lib
run: |
cd build && cmake --build . --config Release --target all -j 10 --
- name: Installing pycasbin
id: installing-pycasbin
run: |
cd build && sudo cmake --build . --config Release --target install -j 10 --
- name: Run Tests
id: run-tests
run: |
cd tests/python && python3 pycasbin_test_suite.py
- name: Cleanup
id: clean-up
run: |
rm -r build
rm tests/python/pycasbin.so
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ MigrationBackup/
*.iml
.vscode
.DS_Store
*.so

# CMake work directory
cmake-build/
4 changes: 4 additions & 0 deletions bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ target_link_libraries(pycasbin
casbin
)

install(
TARGETS pycasbin
DESTINATION ${CMAKE_SOURCE_DIR}/tests/python
)
2 changes: 1 addition & 1 deletion bindings/python/py_enforcer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void bindPyEnforcer(py::module& m) {
.def("BuildRoleLinks", &casbin::Enforcer::BuildRoleLinks, "BuildRoleLinks manually rebuild the role inheritance relations.")
.def("BuildIncrementalRoleLinks", &casbin::Enforcer::BuildIncrementalRoleLinks, "BuildIncrementalRoleLinks provides incremental build the role inheritance relations.")
// .def("Enforce", py::overload_cast<casbin::Scope>(&casbin::Enforcer::Enforce), "Enforce decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).")
.def("Enforce", py::overload_cast<const casbin::DataList &>(&casbin::Enforcer::Enforce), "Enforce with a vector param,decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).")
.def("Enforce", py::overload_cast<const casbin::DataVector &>(&casbin::Enforcer::Enforce), "Enforce with a vector param, decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).")
.def("Enforce", py::overload_cast<const casbin::DataMap &>(&casbin::Enforcer::Enforce), "Enforce with a map param, decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).")
// .def("EnforceWithMatcher", py::overload_cast<const std::string &, casbin::Scope>(&casbin::Enforcer::EnforceWithMatcher), "EnforceWithMatcher use a custom matcher to decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is \"\".")
.def("EnforceWithMatcher", py::overload_cast<const std::string &, const casbin::DataList &>(&casbin::Enforcer::EnforceWithMatcher), "EnforceWithMatcher use a custom matcher to decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is \"\".")
Expand Down
59 changes: 59 additions & 0 deletions casbin/enforcer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ bool Enforcer::Enforce(const DataList& params) {
return this->EnforceWithMatcher("", params);
}

bool Enforcer::Enforce(const DataVector& params) {
return this->EnforceWithMatcher("", params);
}

// Enforce with a map param,decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
bool Enforcer::Enforce(const DataMap& params) {
return this->EnforceWithMatcher("", params);
Expand Down Expand Up @@ -504,6 +508,61 @@ bool Enforcer::EnforceWithMatcher(const std::string& matcher, const DataList& pa
return result;
}

// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
bool Enforcer::EnforceWithMatcher(const std::string& matcher, const DataVector& params) {
const std::vector<std::string>& r_tokens = m_model->m["r"].assertion_map["r"]->tokens;

size_t r_cnt = r_tokens.size();
size_t cnt = params.size();

if (cnt != r_cnt)
return false;

Scope scope = InitializeScope();
PushObject(scope, "r");

size_t i = 0;

for(const auto& param : params) {
if(const auto string_param = std::get_if<std::string>(&param)) {
PushStringPropToObject(scope, "r", *string_param, r_tokens[i].substr(2, r_tokens[i].size() - 2));
}
else if(const auto abac_param = std::get_if<std::shared_ptr<ABACData>>(&param)) {
auto data_ptr = *abac_param;
std::string token_name = r_tokens[i].substr(2, r_tokens[i].size() - 2);

PushObjectPropToObject(scope, "r", token_name);

for(auto [attrib_name, attrib_value] : data_ptr->GetAttributes()) {

if(const auto string_value = std::get_if<std::string>(&attrib_value))
PushStringPropToObject(scope, token_name, *string_value, attrib_name);

else if(const auto int_value = std::get_if<int32_t>(&attrib_value))
PushIntPropToObject(scope, token_name, *int_value, attrib_name);

else if(const auto float_value = std::get_if<float>(&attrib_value))
PushFloatPropToObject(scope, token_name, *float_value, attrib_name);

else if(const auto double_value = std::get_if<double>(&attrib_value))
PushDoublePropToObject(scope, token_name, *double_value, attrib_name);

else
throw CasbinEnforcerException("Not a valid type");
}
}
++i;
}

// for (size_t i = 0; i < cnt; i++) {
// PushStringPropToObject(scope, "r", params[i], r_tokens[i].substr(2, r_tokens[i].size() - 2));
// }

bool result = m_enforce(matcher, scope);
DeinitializeScope(scope);
return result;
}

// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object"
// with the operation "action", input parameters are usually: (matcher, sub, obj, act),
// use model matcher by default when matcher is "".
Expand Down
8 changes: 6 additions & 2 deletions casbin/enforcer.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,19 @@ class Enforcer : public IEnforcer {
void BuildIncrementalRoleLinks(policy_op op, const std::string& p_type, const std::vector<std::vector<std::string>>& rules);
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
bool Enforce(Scope scope);
// Enforce with a vector param,decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
// Enforce with a list param, decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
bool Enforce(const DataList& params);
// Enforce with a vector param, decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
bool Enforce(const DataVector& params);
// Enforce with a map param,decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
bool Enforce(const DataMap& params);
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
bool EnforceWithMatcher(const std::string& matcher, Scope scope);
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
bool EnforceWithMatcher(const std::string& matcher, const DataList& params);
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
bool EnforceWithMatcher(const std::string& matcher, const DataVector& params);
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
bool EnforceWithMatcher(const std::string& matcher, const DataMap& params);
// BatchEnforce enforce in batches
std::vector<bool> BatchEnforce(const std::initializer_list<DataList>& requests);
Expand All @@ -190,7 +194,7 @@ class Enforcer : public IEnforcer {
bool HasPolicy(const std::vector<std::string>& params);
bool HasNamedPolicy(const std::string& p_type, const std::vector<std::string>& params);
bool AddPolicy(const std::vector<std::string>& params);
bool AddPolicies(const std::vector<std::vector<std::string>>& rules);
bool AddPolicies(const std::vector<std::vector<std::string>>& rules);
bool AddNamedPolicy(const std::string& p_type, const std::vector<std::string>& params);
bool AddNamedPolicies(const std::string& p_type, const std::vector<std::vector<std::string>>& rules);
bool RemovePolicy(const std::vector<std::string>& params);
Expand Down
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ endif()
if(CASBIN_BUILD_BENCHMARK)
add_subdirectory(benchmarks)
endif()

# if(CASBIN_BUILD_PYTHON_BINDINGS)
# add_subdirectory(python)
# endif()
30 changes: 30 additions & 0 deletions tests/python/config_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2021 The casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

relative_path = '../../'
basic_model_path = relative_path + 'examples/basic_model.conf'
basic_policy_path = relative_path + 'examples/basic_policy.csv'
rbac_model_path = relative_path + 'examples/rbac_model.conf'
rbac_policy_path = relative_path + 'examples/rbac_policy.csv'
rbac_with_resource_roles_model_path = relative_path + 'examples/rbac_with_resource_roles_model.conf'
rbac_with_resource_roles_policy_path = relative_path + 'examples/rbac_with_resource_roles_policy.csv'
rbac_with_domains_model_path = relative_path + 'examples/rbac_with_domains_model.conf'
rbac_with_domains_policy_path = relative_path + 'examples/rbac_with_domains_policy.csv'
keymatch_model_path = relative_path + 'examples/keymatch_model.conf'
keymatch_policy_path = relative_path + 'examples/keymatch_policy.csv'
rbac_with_deny_model_path = relative_path + 'examples/rbac_with_deny_model.conf'
rbac_with_deny_policy_path = relative_path + 'examples/rbac_with_deny_policy.csv'
priority_model_path = relative_path + 'examples/priority_model.conf'
priority_policy_path = relative_path + 'examples/priority_policy.csv'
basic_model_without_spaces_path = relative_path + 'examples/basic_model_without_spaces.conf'
37 changes: 37 additions & 0 deletions tests/python/pycasbin_test_suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2021 The casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest
import sys
import os
import pycasbin
import test_enforcer

def suite():

# top level directory cached on loader instance
suite = unittest.TestSuite()
loader = unittest.TestLoader()

suite.addTest(loader.loadTestsFromModule(test_enforcer))
return suite


if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
test_suite = suite()
result = runner.run(test_suite)
if result.wasSuccessful() == False:
sys.exit(1)
sys.exit(0)
65 changes: 65 additions & 0 deletions tests/python/test_enforcer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2021 The casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pycasbin as casbin
from config_path import *
import unittest

class TestEnforcer(unittest.TestCase):
def setUp(self):
self.initEnforcer(rbac_with_domains_model_path, rbac_with_domains_policy_path)

def initEnforcer(self, model, policy):
self.current_model = model
self.current_policy = policy
self.e = casbin.Enforcer(self.current_model, self.current_policy)

def tearDown(self):
self.e = None

def test_FourParams(self):
self.initEnforcer(rbac_with_domains_model_path, rbac_with_domains_policy_path)

self.assertEqual(self.e.Enforce(['alice', 'domain1', 'data1', 'read']), True)
self.assertEqual(self.e.Enforce(['alice', 'domain1', 'data1', 'write']), True)
self.assertEqual(self.e.Enforce(['alice', 'domain1', 'data2', 'read']), False)
self.assertEqual(self.e.Enforce(['alice', 'domain1', 'data2', 'write']), False)
self.assertEqual(self.e.Enforce(['bob', 'domain2', 'data1', 'read']), False)
self.assertEqual(self.e.Enforce(['bob', 'domain2', 'data1', 'write']), False)
self.assertEqual(self.e.Enforce(['bob', 'domain2', 'data2', 'read']), True)
self.assertEqual(self.e.Enforce(['bob', 'domain2', 'data2', 'write']), True)

def test_ThreeParams(self):
self.initEnforcer(basic_model_without_spaces_path, basic_policy_path)

self.assertEqual(self.e.Enforce([ 'alice', 'data1', 'read' ]), True)
self.assertEqual(self.e.Enforce([ 'alice', 'data1', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'alice', 'data2', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'alice', 'data2', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data1', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data1', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data2', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data2', 'write' ]), True)

def test_VectorParams(self):
self.initEnforcer(basic_model_without_spaces_path, basic_policy_path)

self.assertEqual(self.e.Enforce([ 'alice', 'data1', 'read' ]), True)
self.assertEqual(self.e.Enforce([ 'alice', 'data1', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'alice', 'data2', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'alice', 'data2', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data1', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data1', 'write' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data2', 'read' ]), False)
self.assertEqual(self.e.Enforce([ 'bob', 'data2', 'write' ]), True)

0 comments on commit 3332324

Please sign in to comment.