From b4e43e5fa550ebd1dea1482105d36f9ee365d0dd Mon Sep 17 00:00:00 2001 From: Sven Lange Date: Wed, 11 Oct 2017 22:28:01 +0200 Subject: [PATCH] Add read-only mode that doesn't modify entries This will also make hasattr on LDAPEntry more predictable in situations where we do not want an empty set returned to add values but rather want to know whether or not the entry has that specific attribute or not. --- ldapom/connection.py | 17 ++++++++++++++++- ldapom/entry.py | 2 +- ldapom/error.py | 3 +++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ldapom/connection.py b/ldapom/connection.py index b89fe72..07e583f 100644 --- a/ldapom/connection.py +++ b/ldapom/connection.py @@ -91,7 +91,7 @@ def __init__(self, uri, base, bind_dn, bind_password, cacertfile=None, require_cert=LDAP_OPT_X_TLS_NEVER, timelimit=30, max_retry_reconnect=5, schema_base="cn=subschema", enable_attribute_type_mapping=True, - retrieve_operational_attributes=False): + retrieve_operational_attributes=False, read_only=False): """ :param uri: URI of the server to connect to. :param base: Base DN for LDAP operations. @@ -104,6 +104,8 @@ def __init__(self, uri, base, bind_dn, bind_password, :param enable_attribute_type_mapping: Whether to enable the mapping of LDAP attribute types to corresponding Python types. Requires the schema to be fetched when connecting. If disabled, all attributes will be treated as a multi-value string attribute. + :param read_only: Do not try to modify entries, do not return empty sets on missing + attributes. """ self._base = base self._uri = uri @@ -115,6 +117,7 @@ def __init__(self, uri, base, bind_dn, bind_password, self._timelimit = timelimit self._schema_base = schema_base self._enable_attribute_type_mapping = enable_attribute_type_mapping + self._read_only = read_only self._connect() @@ -337,6 +340,8 @@ def delete(self, entry, recursive=False): :param recursive: If subentries should be deleted recursively. :type recursive: bool """ + if self._read_only: + raise error.LDAPomReadOnlyError if recursive: entries_to_delete = self._connection._search( base=entry.dn, @@ -356,6 +361,9 @@ def rename(self, entry, new_dn): :param new_dn: The DN that the entry should have after the rename. :type new_dn: str """ + if self._read_only: + raise error.LDAPomReadOnlyError + new_rdn, new_parent_dn = new_dn.split(",", 1) if new_parent_dn == entry.parent_dn: new_parent_dn = None @@ -389,6 +397,8 @@ def save(self, entry): :param entry: The entry to save. :type entry: ldapom.LDAPEntry """ + if self._read_only: + raise error.LDAPomReadOnlyError entry_exists = entry.exists() # Refuse to save if attributes have not been fetched or set explicitly. if entry._attributes is None: @@ -482,6 +492,8 @@ def set_password(self, entry, password): :param password: The password to set. :type password: str """ + if self._read_only: + raise error.LDAPomReadOnlyError password_p = ffi.new("char[]", compat._encode_utf8(password)) password_berval = libldap.ber_bvstr(password_p) entry_dn_p = ffi.new("char[]", compat._encode_utf8(entry.dn)) @@ -493,3 +505,6 @@ def set_password(self, entry, password): password_berval, password_berval, ffi.NULL, ffi.NULL) handle_ldap_error(err) + + def is_read_only(self): + return self._read_only diff --git a/ldapom/entry.py b/ldapom/entry.py index 87d0f84..04660f2 100644 --- a/ldapom/entry.py +++ b/ldapom/entry.py @@ -86,7 +86,7 @@ def __getattr__(self, name): else: return attribute.values else: - if attribute_type.multi_value: + if attribute_type.multi_value and not self._connection.is_read_only(): setattr(self, name, set()) return self.get_attribute(name).values else: diff --git a/ldapom/error.py b/ldapom/error.py index 4b94657..5ccbca7 100644 --- a/ldapom/error.py +++ b/ldapom/error.py @@ -29,3 +29,6 @@ class LDAPAttributeNameNotFoundError(LDAPomError): class LDAPCouldNotFetchAttributeTypes(LDAPomError): pass + +class LDAPomReadOnlyError(LDAPomError): + pass