diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa92a0e4..da315ff2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,10 +33,15 @@ jobs: id: building-lib run: | cd build && cmake --build . --config Debug --target all -j 10 -- + - name: Tests + id: test-lib + run: | + cd build + ctest -j10 -C Debug -T test --output-on-failure -T test --output-on-failure - name: Cleanup id: clean-up run: | - rm -r build lib + rm -r build windows: name: "Windows 10 (MSVC 19.29)" @@ -55,12 +60,16 @@ jobs: id: building-lib run: | cd build - cmake --build . --config Release --target casbin -j 10 -- + cmake --build . --config Debug --target casbin gtest gtest_main gmock gmock_main casbintest -j 10 -- + - name: Tests + id: test-lib + run: | + cd build + ctest -j10 -C Debug -T test --output-on-failure -T test --output-on-failure - name: Cleanup id: clean-up run: | rm -r build - rm -r lib macos: name: "macOS Catalina 10.15 (AppleClang 12.0)" @@ -76,7 +85,12 @@ jobs: - name: Building library id: building-lib run: | - cd build && cmake --build . --config Debug --target all -j 10 -- + cd build && cmake --build . --config Debug --target all install -j 10 -- + - name: Tests + id: test-lib + run: | + cd build + ctest -j10 -C Debug -T test --output-on-failure -T test --output-on-failure - name: Cleanup id: clean-up run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 6da0a7b1..6a238b0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,24 @@ +# Copyright 2020 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. + cmake_minimum_required(VERSION 3.19) +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + ${CMAKE_SOURCE_DIR}/cmake/modules +) + set(CMAKE_WARN_DEPRECATED ON) if(APPLE AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) @@ -34,6 +53,9 @@ if(NOT DEFINED CMAKE_INSTALL_MESSAGE) set(CMAKE_INSTALL_MESSAGE "NEVER") endif() +enable_testing() +add_subdirectory(tests) + # Change the path max size to avoid problem on Windows. if(NOT DEFINED CMAKE_OBJECT_PATH_MAX) set(CMAKE_OBJECT_PATH_MAX 300) @@ -42,4 +64,12 @@ endif() # Setting to C++ standard to C++17 set(CMAKE_CXX_STANDARD 17) +############################################################################### +# Find or install external dependencies +# Some required targets may be created by third-party CMake configs, which +# don't generally produce global targets. To guarantee all imported targets are +# global, this module is included at the project root level. + +include(FindExtPackages) + add_subdirectory(casbin) diff --git a/casbin/CMakeLists.txt b/casbin/CMakeLists.txt index 37ca1be3..c05ba401 100644 --- a/casbin/CMakeLists.txt +++ b/casbin/CMakeLists.txt @@ -1,4 +1,16 @@ -set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) +# Copyright 2020 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. FILE(GLOB_RECURSE SRC_FILES "*.cpp" "*.h") @@ -7,9 +19,22 @@ include_directories(${CMAKE_SOURCE_DIR}/casbin) target_precompile_headers(casbin PUBLIC "pch.h") +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}) + set_target_properties(casbin PROPERTIES PREFIX "") if(WIN32 OR MSVC) set_target_properties(casbin PROPERTIES SUFFIX ".lib") elseif(UNIX) set_target_properties(casbin PROPERTIES SUFFIX ".a") endif() + +install( + TARGETS casbin + DESTINATION lib +) + +install( + DIRECTORY ${CMAKE_SOURCE_DIR}/casbin + DESTINATION ${CMAKE_SOURCE_DIR}/lib + FILES_MATCHING PATTERN "*.h" +) diff --git a/casbin/casbin.h b/casbin/casbin.h new file mode 100644 index 00000000..34775be4 --- /dev/null +++ b/casbin/casbin.h @@ -0,0 +1,22 @@ +/* +* Copyright 2020 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. +* +* This file is the root header which is to be included by the client to use +* casbin in C++ environment +*/ + +#include "enforcer.h" +#include "enforcer_cached.h" +#include "enforcer_synced.h" diff --git a/casbin/enforcer_synced.cpp b/casbin/enforcer_synced.cpp index 4a5daab4..54f8e9d8 100644 --- a/casbin/enforcer_synced.cpp +++ b/casbin/enforcer_synced.cpp @@ -110,7 +110,7 @@ void SyncedEnforcer ::StartAutoLoadPolicy(std::chrono::duration(new Ticker(onTick, t)); + ticker = std::make_unique(onTick, t); n = 1; ticker->start(); } diff --git a/casbin/util/ticker.h b/casbin/util/ticker.h index 3725df06..b6a8363f 100644 --- a/casbin/util/ticker.h +++ b/casbin/util/ticker.h @@ -44,7 +44,7 @@ class Ticker { void timer_loop(); on_tick_t _onTick; tick_interval_t _tickInterval; - std::atomic_bool _running; + std::atomic_bool _running; std::mutex _tickIntervalMutex; future_vec _futures1; future_vec _futures2; diff --git a/cmake/modules/FindExtPackages.cmake b/cmake/modules/FindExtPackages.cmake new file mode 100644 index 00000000..45225ee9 --- /dev/null +++ b/cmake/modules/FindExtPackages.cmake @@ -0,0 +1,15 @@ +############################################################################### +### Global package options ### + +set(CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY ON CACHE BOOL + "Disable CMake User Package Registry when finding packages") + +set(CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY ON CACHE BOOL + "Disable CMake System Package Registry when finding packages") + +############################################################################### +### Packages and versions ### + +# googletest +# https://github.com/google/googletest +find_package(googletest 1.11.0 REQUIRED) diff --git a/cmake/modules/Findgoogletest.cmake b/cmake/modules/Findgoogletest.cmake new file mode 100644 index 00000000..e0187da7 --- /dev/null +++ b/cmake/modules/Findgoogletest.cmake @@ -0,0 +1,8 @@ +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..5623e96e --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright 2020 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. + +set(CMAKE_CXX_STANDARD 17) + +add_executable( + casbintest + enforcer_test.cpp + enforcer_cached_test.cpp + enforcer_synced_test.cpp +) + +target_include_directories(casbintest PUBLIC ${CMAKE_SOURCE_DIR}) + +target_link_libraries( + casbintest + gtest_main + casbin +) + +include(GoogleTest) +gtest_discover_tests(casbintest) diff --git a/tests/enforcer_cached_test.cpp b/tests/enforcer_cached_test.cpp new file mode 100644 index 00000000..6242c0b9 --- /dev/null +++ b/tests/enforcer_cached_test.cpp @@ -0,0 +1,46 @@ +/* +* Copyright 2020 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. +* +* This is a test file showcasing the workflow of casbin::CachedEnforcer +*/ + +#include +#include + +TEST(TestEnforcerCached, TestCache) { + std::string model = "../../examples/basic_model.conf"; + std::string policy = "../../examples/basic_policy.csv"; + casbin::CachedEnforcer e(model, policy); + ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), true); + ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); + + // The cache is enabled, so even if we remove a policy rule, the decision + // for ("alice", "data1", "read") will still be true, as it uses the cached result. + e.RemovePolicy({ "alice", "data1", "read" }); + ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), true); + ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); + + // Now we invalidate the cache, then all first-coming Enforce() has to be evaluated in real-time. + // The decision for ("alice", "data1", "read") will be false now. + e.InvalidateCache(); + ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); +} \ No newline at end of file diff --git a/tests/enforcer_synced_test.cpp b/tests/enforcer_synced_test.cpp new file mode 100644 index 00000000..327bd6ae --- /dev/null +++ b/tests/enforcer_synced_test.cpp @@ -0,0 +1,68 @@ +/* +* Copyright 2020 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. +* +* This is a test file showcasing the workflow of casbin::CachedEnforcer +*/ + +#include +#include + +// TEST(TestEnforcerSynced, TestSync) { +// std::string model = "../../examples/basic_model.conf"; +// std::string policy = "../../examples/basic_policy.csv"; +// casbin::SyncedEnforcer e(model, policy); + +// using namespace std::literals::chrono_literals; +// auto time1 = 200ms; +// e.StartAutoLoadPolicy(time1); + +// EXPECT_TRUE(e.Enforce({ "alice", "data1", "read" })); +// EXPECT_FALSE(e.Enforce({ "alice", "data1", "write" })); +// EXPECT_FALSE(e.Enforce({ "alice", "data2", "read" })); +// EXPECT_FALSE(e.Enforce({ "alice", "data2", "write" })); +// EXPECT_FALSE(e.Enforce({ "bob", "data1", "read" })); +// EXPECT_FALSE(e.Enforce({ "bob", "data1", "write" })); +// EXPECT_FALSE(e.Enforce({ "bob", "data2", "read" })); +// EXPECT_TRUE(e.Enforce({ "bob", "data2", "write" })); + +// e.StopAutoLoadPolicy(); +// } + +// TEST(TestEnforcerSynced, TestStopLoadPolicy) { +// std::string model = "../../examples/basic_model.conf"; +// std::string policy = "../../examples/basic_policy.csv"; +// casbin::SyncedEnforcer e(model, policy); + +// using namespace std::literals::chrono_literals; +// std::chrono::duration t = 5ms; + +// e.StartAutoLoadPolicy(t); + +// EXPECT_EQ(e.IsAutoLoadingRunning(), true); + +// ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), true); +// ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); +// ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); +// ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); +// ASSERT_EQ(e.Enforce({ "bob", "data1", "read" }), false); +// ASSERT_EQ(e.Enforce({ "bob", "data1", "write" }), false); +// ASSERT_EQ(e.Enforce({ "bob", "data2", "read" }), false); +// ASSERT_EQ(e.Enforce({ "bob", "data2", "write" }), true); + +// e.StopAutoLoadPolicy(); +// std::this_thread::sleep_for(10ms); + +// EXPECT_EQ(e.IsAutoLoadingRunning(), false); +// } diff --git a/tests/enforcer_test.cpp b/tests/enforcer_test.cpp new file mode 100644 index 00000000..562feca2 --- /dev/null +++ b/tests/enforcer_test.cpp @@ -0,0 +1,95 @@ +/* +* Copyright 2020 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. +* +* This is a test file showcasing the workflow of casbin::Enforcer +*/ + +#include +#include + +TEST(TestEnforcer, TestFourParams) { + std::string model = "../../examples/rbac_with_domains_model.conf"; + std::string policy = "../../examples/rbac_with_domains_policy.csv"; + casbin::Enforcer e = casbin::Enforcer(model, policy); + + ASSERT_EQ(e.Enforce({ "alice", "domain1", "data1", "read" }), true); + ASSERT_EQ(e.Enforce({ "alice", "domain1", "data1", "write" }), true); + ASSERT_EQ(e.Enforce({ "alice", "domain1", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "domain1", "data2", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "domain2", "data1", "read" }), false); + ASSERT_EQ(e.Enforce({ "bob", "domain2", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "domain2", "data2", "read" }), true); + ASSERT_EQ(e.Enforce({ "bob", "domain2", "data2", "write" }), true); +} + +TEST(TestEnforcer, TestThreeParams) { + std::string model = "../../examples/basic_model_without_spaces.conf"; + std::string policy = "../../examples/basic_policy.csv"; + casbin::Enforcer e(model, policy); + + ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), true); + ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data1", "read" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data2", "write" }), true); +} + +TEST(TestEnforcer, TestVectorParams) { + std::string model = "../../examples/basic_model_without_spaces.conf"; + std::string policy = "../../examples/basic_policy.csv"; + casbin::Enforcer e(model, policy); + + ASSERT_EQ(e.Enforce({ "alice", "data1", "read" }), true); + ASSERT_EQ(e.Enforce({ "alice", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "alice", "data2", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data1", "read" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data1", "write" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data2", "read" }), false); + ASSERT_EQ(e.Enforce({ "bob", "data2", "write" }), true); +} + +TEST(TestEnforcer, TestMapParams) { + std::string model = "../../examples/basic_model_without_spaces.conf"; + std::string policy = "../../examples/basic_policy.csv"; + casbin::Enforcer e(model, policy); + + std::unordered_map params = {{"sub", "alice"}, {"obj", "data1"}, {"act", "read"}}; + ASSERT_EQ(e.Enforce(params), true); + + params = { {"sub","alice"},{"obj","data1"},{"act","write"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","alice"},{"obj","data2"},{"act","read"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","alice"},{"obj","data2"},{"act","write"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","bob"},{"obj","data1"},{"act","read"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","bob"},{"obj","data1"},{"act","write"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","bob"},{"obj","data2"},{"act","read"} }; + ASSERT_EQ(e.Enforce(params), false); + + params = { {"sub","bob"},{"obj","data2"},{"act","write"} }; + ASSERT_EQ(e.Enforce(params), true); +}