From a3d527c59efe785bac62b4e1deef5f6ba961431c Mon Sep 17 00:00:00 2001 From: Jason Peacock Date: Thu, 7 Nov 2024 16:09:34 -0600 Subject: [PATCH] Improve API regarding SNMP v3 credentials. The Authentication, Privacy, and related classes now support equality operations. The Session.create_users method now assumes it's passed objects of type UsmUser and excludes incomplete security options from the argument string. ZEN-35108 --- pynetsnmp/netsnmp.py | 30 ++++++++++++++-------- pynetsnmp/security.py | 59 +++++++++++++++++++++++++++++++++++++++---- pynetsnmp/usm.py | 17 ++++++++++--- 3 files changed, 86 insertions(+), 20 deletions(-) diff --git a/pynetsnmp/netsnmp.py b/pynetsnmp/netsnmp.py index c5d6c39..43dab9d 100644 --- a/pynetsnmp/netsnmp.py +++ b/pynetsnmp/netsnmp.py @@ -858,20 +858,28 @@ def awaitTraps( def create_users(self, users): self._log.debug("create_users: Creating %s users.", len(users)) for user in users: - if user.version != SNMP_VERSION_3: + if str(user.version) != str(SNMP_VERSION_3): + self._log.info("create_users: user is not v3 %s", user) continue try: line = "" - if user.engine_id: - line = "-e '{}' ".format(user.engine_id) - line += "'{}' '{}' '{}' '{}' '{}'".format( - _escape_char("'", user.username), - _escape_char("'", user.authentication_type), - _escape_char("'", user.authentication_passphrase), - _escape_char("'", user.privacy_protocol), - _escape_char("'", user.privacy_passphrase), - ) - lib.usm_parse_create_usmUser("createUser", line) + if user.engine: + line = "-e '{}'".format(user.engine) + if user.name: + line += " '{}'".format( + _escape_char("'", user.name), + ) + if user.auth: + line += " '{}' '{}'".format( + _escape_char("'", user.auth.protocol.name), + _escape_char("'", user.auth.passphrase), + ) + if user.priv: + line += " '{}' '{}'".format( + _escape_char("'", user.priv.protocol.name), + _escape_char("'", user.priv.passphrase), + ) + lib.usm_parse_create_usmUser("createUser", line.strip()) self._log.debug("create_users: created user: %s", user) except Exception as e: self._log.debug( diff --git a/pynetsnmp/security.py b/pynetsnmp/security.py index b01bb9b..21e4b4f 100644 --- a/pynetsnmp/security.py +++ b/pynetsnmp/security.py @@ -40,7 +40,7 @@ def __init__(self, name, auth=None, priv=None, engine=None, context=None): def getArguments(self): auth = ( - ("-a", str(self.auth.protocol), "-A", self.auth.passphrase) + ("-a", self.auth.protocol.name, "-A", self.auth.passphrase) if self.auth else () ) @@ -48,7 +48,7 @@ def getArguments(self): # The privacy arguments are only given if the authentication # arguments are also provided. priv = ( - ("-x", str(self.priv.protocol), "-X", self.priv.passphrase) + ("-x", self.priv.protocol.name, "-X", self.priv.passphrase) if self.priv else () ) @@ -69,6 +69,31 @@ def getArguments(self): + (("-n", self.context) if self.context else ()) ) + def __eq__(self, other): + return ( + self.name == other.name + and self.auth == other.auth + and self.priv == other.priv + and self.engine == other.engine + and self.context == other.context + ) + + def __str__(self): + info = ", ".join( + "{0}={1}".format(k, v) + for k, v in ( + ("name", self.name), + ("auth", self.auth), + ("priv", self.priv), + ("engine", self.engine), + ("context", self.context), + ) + if v + ) + return "{0.__class__.__name__}(version={0.version}{1}{2})".format( + self, ", " if info else "", info + ) + _sec_level = {(True, True): "authPriv", (True, False): "authNoPriv"} _version_map = { @@ -86,6 +111,8 @@ class Authentication(object): Provides the authentication data for UsmUser objects. """ + __slots__ = ("protocol", "passphrase") + def __init__(self, protocol, passphrase): if protocol is None: raise ValueError( @@ -93,17 +120,28 @@ def __init__(self, protocol, passphrase): ) self.protocol = auth_protocols[protocol] if not passphrase: - raise ValueError( - "Authentication protocol requires a passphrase" - ) + raise ValueError("Authentication protocol requires a passphrase") self.passphrase = passphrase + def __eq__(self, other): + if not isinstance(other, Authentication): + return NotImplemented + return ( + self.protocol == other.protocol + and self.passphrase == other.passphrase + ) + + def __str__(self): + return "{0.__class__.__name__}(protocol={0.protocol})".format(self) + class Privacy(object): """ Provides the privacy data for UsmUser objects. """ + __slots__ = ("protocol", "passphrase") + def __init__(self, protocol, passphrase): if protocol is None: raise ValueError("Invalid Privacy protocol '{}'".format(protocol)) @@ -111,3 +149,14 @@ def __init__(self, protocol, passphrase): if not passphrase: raise ValueError("Privacy protocol requires a passphrase") self.passphrase = passphrase + + def __eq__(self, other): + if not isinstance(other, Privacy): + return NotImplemented + return ( + self.protocol == other.protocol + and self.passphrase == other.passphrase + ) + + def __str__(self): + return "{0.__class__.__name__}(protocol={0.protocol})".format(self) diff --git a/pynetsnmp/usm.py b/pynetsnmp/usm.py index bccd675..2e9a422 100644 --- a/pynetsnmp/usm.py +++ b/pynetsnmp/usm.py @@ -1,15 +1,24 @@ +from __future__ import absolute_import + class _Protocol(object): - __slots__ = ("__name",) + """ """ + + __slots__ = ("name",) def __init__(self, name): - self.__name = name + self.name = name + + def __eq__(self, other): + if not isinstance(other, type(self)): + return NotImplemented + return self.name == other.name def __str__(self): - return self.__name + return self.name def __repr__(self): return "<{0.__module__}.{0.__name__} {1}>".format( - self.__class__, self.__name + self.__class__, self.name )