From ad070266f9347f159874e6b2fa57302385f354e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 9 Apr 2025 14:51:31 +0200 Subject: [PATCH 17/20] wrap sr_nacm_check_operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Organization: Wires Sysrepo provides API function for explicit checking if an operation can be authorized on a node. This might come handy if one decides to do NACM authorization himself, like we will do in our RESTCONF server. Change-Id: Ida41514a7f03ab120a331363b7f9ed8b69918d88 Signed-off-by: Mattias Walström --- include/sysrepo-cpp/Session.hpp | 1 + src/Session.cpp | 13 ++++++++++ tests/session.cpp | 46 +++++++++++++++++++++++++++++++++ tests/test_module.yang | 4 +++ 4 files changed, 64 insertions(+) diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp index 1c409b8..2112d21 100644 --- a/include/sysrepo-cpp/Session.hpp +++ b/include/sysrepo-cpp/Session.hpp @@ -101,6 +101,7 @@ public: void setNacmUser(const std::string& user); std::optional getNacmUser() const; + bool checkNacmOperation(const libyang::DataNode& node) const; [[nodiscard]] Subscription initNacm( SubscribeOptions opts = SubscribeOptions::Default, ExceptionHandler handler = nullptr, diff --git a/src/Session.cpp b/src/Session.cpp index 2273c79..ce0dec2 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -715,6 +715,19 @@ std::optional Session::getNacmUser() const return username ? std::make_optional(username) : std::nullopt; } +/** + * @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`. + * @return true if the current user is authorized to perform operation on given @p node. + * + * Details on unsuccessfull authorizations can be retrieved via Session::getErrors. + * Note that if the NACM user is not set, `sr_nacm_check_operation` and this function both return true. + */ +bool Session::checkNacmOperation(const libyang::DataNode& node) const +{ + auto res = sr_nacm_check_operation(m_sess.get(), libyang::getRawNode(node)); + return res == SR_ERR_OK; +} + /** * @brief Initializes NACM callbacks. * diff --git a/tests/session.cpp b/tests/session.cpp index 548b371..35dd545 100644 --- a/tests/session.cpp +++ b/tests/session.cpp @@ -423,6 +423,52 @@ TEST_CASE("session") sysrepo::ErrorWithCode); } + DOCTEST_SUBCASE("Session::checkNacmOperation") + { + auto nacmSub = sess.initNacm(); + + // check NACM access for RPCs + auto shutdownRPC = sess.getContext().newPath("/test_module:shutdown", std::nullopt); + auto denyAllRPC = sess.getContext().newPath("/test_module:deny-all-rpc", std::nullopt); + + // user not set, everything is permitted + REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); + REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); + REQUIRE(sess.getErrors().size() == 0); + + sess.setNacmUser("root"); + REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); + REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); + REQUIRE(sess.getErrors().size() == 0); + + sess.setNacmUser("nobody"); + REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); + REQUIRE(sess.checkNacmOperation(denyAllRPC) == false); + REQUIRE(sess.getErrors().size() == 1); + REQUIRE(sess.getErrors().at(0) == sysrepo::ErrorInfo{ + .code = sysrepo::ErrorCode::Unauthorized, + .errorMessage = "Executing the operation is denied because \"nobody\" NACM authorization failed.", + }); + + sess.setNacmUser("root"); // 'nobody' is not authorized to write into this subtree + sess.switchDatastore(sysrepo::Datastore::Running); + sess.setItem("/ietf-netconf-acm:nacm/enable-external-groups", "false"); + sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='grp']/user-name[.='nobody']", ""); + sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/group[.='grp']", ""); + sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/module-name", "test_module"); + sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/access-operations", "*"); + sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/action", "deny"); + sess.applyChanges(); + + sess.setNacmUser("root"); + REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); + REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); + + sess.setNacmUser("nobody"); + REQUIRE(sess.checkNacmOperation(denyAllRPC) == false); + REQUIRE(sess.checkNacmOperation(shutdownRPC) == false); + } + DOCTEST_SUBCASE("Session::getPendingChanges") { REQUIRE(sess.getPendingChanges() == std::nullopt); diff --git a/tests/test_module.yang b/tests/test_module.yang index 02c467b..3d6c26d 100644 --- a/tests/test_module.yang +++ b/tests/test_module.yang @@ -55,6 +55,10 @@ module test_module { rpc noop { } + rpc deny-all-rpc { + nacm:default-deny-all; + } + rpc shutdown { output { leaf success { -- 2.43.0