diff --git a/cffi/cdefs.h b/cffi/cdefs.h index b820eef..dd7bd18 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -221,6 +221,11 @@ int sr_get_event_pipe(sr_subscription_ctx_t *, int *); int sr_subscription_process_events(sr_subscription_ctx_t *, sr_session_ctx_t *, struct timespec *); int sr_unsubscribe(sr_subscription_ctx_t *); +int sr_nacm_init(sr_session_ctx_t *, sr_subscr_options_t, sr_subscription_ctx_t **); +int sr_nacm_set_user(sr_session_ctx_t *, const char *); +const char* sr_nacm_get_user(sr_session_ctx_t *); +void sr_nacm_destroy(void); + typedef enum sr_event_e { SR_EV_UPDATE, SR_EV_CHANGE, diff --git a/cffi/source.c b/cffi/source.c index ec4fe51..5f754f2 100644 --- a/cffi/source.c +++ b/cffi/source.c @@ -5,6 +5,7 @@ #include #include +#include #if (SR_VERSION_MAJOR != 7) #error "This version of sysrepo bindings only works with libsysrepo.so.7" diff --git a/sysrepo/session.py b/sysrepo/session.py index f08cf10..193ab00 100644 --- a/sysrepo/session.py +++ b/sysrepo/session.py @@ -39,6 +39,7 @@ class SysrepoSession: "cdata", "is_implicit", "subscriptions", + "nacm_subscription", ) # begin: general @@ -52,10 +53,33 @@ def __init__(self, cdata, implicit: bool = False): self.cdata = cdata self.is_implicit = implicit self.subscriptions = [] + self.nacm_subscription = None def __enter__(self) -> "SysrepoSession": return self + def init_nacm(self, nacm_user: str, no_thread: bool = False) -> None: + """ + Set the NACM user for this session. This is used to determine the effective user + for NACM checks. + + :arg nacm_user: + The NACM user name to be set. + :arg no_thread: + If True, no thread will be created for handling NACM subscription meaning. + Default to False. + """ + flags = _subscribe_flags( + no_thread=no_thread, + ) + if self.is_implicit: + raise SysrepoUnsupportedError("cannot set NACM for implicit sessions") + + sub_p = ffi.new("sr_subscription_ctx_t **") + check_call(lib.sr_nacm_init, self.cdata, flags, sub_p) + check_call(lib.sr_nacm_set_user, self.cdata, str2c(nacm_user)) + self.nacm_subscription = sub_p + def __exit__(self, *args, **kwargs) -> None: self.stop() @@ -70,6 +94,10 @@ def stop(self) -> None: return # already stopped if self.is_implicit: raise SysrepoUnsupportedError("implicit sessions cannot be stopped") + if self.nacm_subscription is not None: + check_call(lib.sr_unsubscribe, self.nacm_subscription[0]) + self.nacm_subscription = None + lib.sr_nacm_destroy() # clear subscriptions while self.subscriptions: diff --git a/tests/test_session.py b/tests/test_session.py index f100503..38a0954 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -206,3 +206,44 @@ def function_thread_two(): thread_one.join() thread_two.join() + + def test_nacm(self): + with self.conn.start_session("running") as sess: + config = {"conf": {"system": {"hostname": "foobar1"}}} + sess.replace_config(config, "sysrepo-example") + nacm_config = { + "ietf-netconf-acm:nacm": { + "enable-nacm": True, + "read-default": "deny", + "groups": { + "group": [ + {"name": "admin", "user-name": ["john"]}, + ] + }, + "rule-list": [ + { + "name": "sysrepo-example-permit", + "group": ["admin"], + "rule": [ + { + "name": "sysrepo-example-permit-read", + "module-name": "sysrepo-example", + "access-operations": "read", + "action": "permit", + } + ], + } + ], + } + } + sess.edit_batch(nacm_config, "ietf-netconf-acm", strict=True) + sess.apply_changes() + + # read access to sysrepo-example is allowed, but write is not for user 'john' + with self.conn.start_session("running") as sess: + sess.init_nacm("john") + data = sess.get_data("/sysrepo-example:conf") + self.assertEqual(data["conf"]["system"]["hostname"], "foobar1") + with self.assertRaises(sysrepo.SysrepoUnauthorizedError): + sess.set_item("/sysrepo-example:conf/system/hostname", "barfoo1") + sess.apply_changes()