def delete(self): """Deletes the backend, it's mapping tree and all related indices. This can be changed with the self._protected flag! :raises: - UnwillingToPerform - if backend is protected - UnwillingToPerform - if nsslapd-state is not 'backend' """ if self._protected: raise ldap.UNWILLING_TO_PERFORM("This is a protected backend!") # First check if the mapping tree has our suffix still. # suffix = self.get_attr_val('nsslapd-suffix') bename = self.get_attr_val_utf8('cn') try: mt = self._mts.get(selector=bename) # Assert the type is "backend" # Are these the right types....? if mt.get_attr_val('nsslapd-state').lower() != ensure_bytes('backend'): raise ldap.UNWILLING_TO_PERFORM('Can not delete the mapping tree, not for a backend! You may need to delete this backend via cn=config .... ;_; ') # Delete our mapping tree if it exists. mt.delete() except ldap.NO_SUCH_OBJECT: # Righto, it's already gone! Do nothing ... pass # Delete all our related indices self._instance.index.delete_all(bename) # Now remove our children, this is all ldbm config configs = self._instance.search_s(self._dn, ldap.SCOPE_ONELEVEL) for c in configs: self._instance.delete_branch_s(c.dn, ldap.SCOPE_SUBTREE) # The super will actually delete ourselves. super(Backend, self).delete()
def _validate(self, rdn, properties): """Validate the factory part of the creation""" if properties is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') if type(properties) != dict: raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") return (rdn, properties)
def get(self, selector=[], dn=None, json=False): """Get a child entry (DSLdapObject, Replica, etc.) with dn or selector using a base DN and objectClasses of our object (DSLdapObjects, Replicas, etc.) Note that * is not a valid selector, you should use "list()" instead. :param dn: DN of wanted entry :type dn: str :param selector: An additional filter to search for, i.e. 'backend_name'. The attributes selected are based on object type, ie user will search for uid and cn. :type dn: str :returns: A child entry """ results = [] if dn is not None: results = self._get_dn(dn) else: results = self._get_selector(selector) if len(results) == 0: raise ldap.NO_SUCH_OBJECT("No object exists given the filter criteria %s" % selector) if len(results) > 1: raise ldap.UNWILLING_TO_PERFORM("Too many objects matched selection criteria %s" % selector) if json: return self._entry_to_instance(results[0].dn, results[0]).get_all_attrs_json() else: return self._entry_to_instance(results[0].dn, results[0])
def _validate(self, rdn, properties): """ An internal implementation detail of create verification. You should never call this directly. """ if self._basedn == DN_MAPPING_TREE: raise ldap.UNWILLING_TO_PERFORM( "Refusing to create agreement in %s" % DN_MAPPING_TREE) return super(Agreements, self)._validate(rdn, properties)
def _validate(self, rdn, properties, basedn): # We always need to call the super validate first. This way we can # guarantee that properties is a dictionary. # However, backend can take different properties. One is # based on the actual key, value of the object # one is the "python magic" types. # So we actually have to do the super validation later. if properties is None: raise ldap.UNWILLING_TO_PERFORM( 'Invalid request to create. Properties cannot be None') if type(properties) != dict: raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") # This is converting the BACKEND_ types to the DS nsslapd- attribute values nprops = {} for key, value in properties.items(): try: nprops[BACKEND_PROPNAME_TO_ATTRNAME[key]] = [ value, ] except KeyError: # This means, it's not a mapped value, so continue nprops[key] = value (dn, valid_props) = super(Backend, self)._validate(rdn, nprops, basedn) try: self._mts.get(ensure_str(valid_props['nsslapd-suffix'][0])) raise ldap.UNWILLING_TO_PERFORM( "Mapping tree for this suffix exists!") except ldap.NO_SUCH_OBJECT: pass try: self._mts.get(ensure_str(valid_props['cn'][0])) raise ldap.UNWILLING_TO_PERFORM( "Mapping tree for this database exists!") except ldap.NO_SUCH_OBJECT: pass # We have to stash our valid props so that mapping tree can use them ... self._nprops_stash = valid_props return (dn, valid_props)
def delete(self): """Deletes the backend, it's mapping tree and all related indices. This can be changed with the self._protected flag! :raises: - UnwillingToPerform - if backend is protected - UnwillingToPerform - if nsslapd-state is not 'backend' """ if self._protected: raise ldap.UNWILLING_TO_PERFORM("This is a protected backend!") # First check if the mapping tree has our suffix still. # suffix = self.get_attr_val('nsslapd-suffix') bename = self.get_attr_val_utf8('cn') try: mt = self._mts.get(selector=bename) # Assert the type is "backend" # Are these the right types....? if mt.get_attr_val_utf8('nsslapd-state').lower() != 'backend': raise ldap.UNWILLING_TO_PERFORM( 'Can not delete the mapping tree, not for a backend! You may need to delete this backend via cn=config .... ;_; ' ) # Delete replicas first try: Replicas(self._instance).get( mt.get_attr_val_utf8('cn')).delete() except ldap.NO_SUCH_OBJECT: # No replica, no problem pass # Delete our mapping tree if it exists. mt.delete() except ldap.NO_SUCH_OBJECT: # Righto, it's already gone! Do nothing ... pass # Now remove our children, this is all ldbm config self._instance.delete_branch_s(self._dn, ldap.SCOPE_SUBTREE)
def _validate(self, rdn, properties, basedn): """Used to validate a create request. This way, it can be over-ridden without affecting the create types. It also checks that all the values in _must_attribute exist in some form in the dictionary. It has the useful trick of returning the dn, so subtypes can use extra properties to create the dn's here for this. """ if properties is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') if type(properties) != dict: raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") # I think this needs to be made case insensitive # How will this work with the dictionary? if self._must_attributes is not None: for attr in self._must_attributes: if properties.get(attr, None) is None: # Put RDN to properties if attr == self._rdn_attribute and rdn is not None: properties[self._rdn_attribute] = ldap.dn.str2dn(rdn)[0][0][1] else: raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None' % attr) # Make sure the naming attribute is present if properties.get(self._rdn_attribute, None) is None and rdn is None: raise ldap.UNWILLING_TO_PERFORM('Attribute %s must not be None or rdn provided' % self._rdn_attribute) # Great! Now, lets fix up our types for k, v in properties.items(): if isinstance(v, list): # Great! pass else: # Turn it into a list instead. properties[k] = [v, ] # If we were created with a dn= in the object init, we set tdn now, and skip # any possible dn derivation steps that follow. tdn = self._dn # However, if no DN was provided, we attempt to derive the DN from the relevant # properties of the object. The idea being that by defining only the attributes # of the object, we can just solve a possible rdn instead of asking for the same # data twice. if tdn is None: if basedn is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. basedn cannot be None') # Were we given a relative component? Yes? Go ahead! if rdn is not None: tdn = ensure_str('%s,%s' % (rdn, basedn)) elif properties.get(self._rdn_attribute, None) is not None: # Favour the value in the properties dictionary v = properties.get(self._rdn_attribute) rdn = ensure_str(v[0]) erdn = ensure_str(ldap.dn.escape_dn_chars(rdn)) self._log.debug("Using first property %s: %s as rdn" % (self._rdn_attribute, erdn)) # Now we compare. If we changed this value, we have to put it back to make the properties complete. if erdn != rdn: properties[self._rdn_attribute].append(erdn) tdn = ensure_str('%s=%s,%s' % (self._rdn_attribute, erdn, basedn)) # We may need to map over the data in the properties dict to satisfy python-ldap str_props = {} for k, v in properties.items(): str_props[k] = ensure_list_bytes(v) # # Do we need to do extra dn validation here? return (tdn, str_props)
def delete(self, suffix=None, bename=None, name=None): ''' Delete a mapping tree entry (under "cn=mapping tree,cn=config"), for the 'suffix' and that is stored in 'benamebase' backend. 'benamebase' backend is not changed by the mapping tree deletion. If 'name' is specified. It uses it to retrieve the mapping tree to delete. Else if 'suffix'/'benamebase' are specified. It uses both to retrieve the mapping tree to delete @param suffix - suffix mapped by this mapping tree entry. It is the common name ('cn') of the entry @param benamebase - backend common name (e.g. 'userRoot') @param name - DN of the mapping tree entry @return None @raise ldap.NO_SUCH_OBJECT - the entry is not found KeyError if 'name', 'suffix' and 'benamebase' are missing UnwillingToPerformError - If the mapping tree has subordinates ''' if name: filt = "(objectclass=%s)" % MT_OBJECTCLASS_VALUE try: ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt) self.log.debug("delete: %s found by its DN", ent.dn) except NoSuchEntryError: raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" % name) else: filt = None if suffix: filt = suffixfilt(suffix) if bename: if filt: filt = ( "(&(%s=%s)%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename, filt)) else: filt = ("(%s=%s)" % (MT_PROPNAME_TO_ATTRNAME[MT_BACKEND], bename)) try: ent = self.conn.getEntry(DN_MAPPING_TREE, ldap.SCOPE_ONELEVEL, filt) self.log.debug("delete: %s found by with %s", ent.dn, filt) except NoSuchEntryError: raise ldap.NO_SUCH_OBJECT("mapping tree DN not found: %s" % name) # # At this point 'ent' contains the mapping tree entry to delete # # First Check there is no child (replica, replica agreements) try: ents = self.conn.search_s(ent.dn, ldap.SCOPE_SUBTREE, "objectclass=*") except: raise if len(ents) != 1: for entry in ents: self.log.warning("Error: it exists %s under %s", entry.dn, ent.dn) raise ldap.UNWILLING_TO_PERFORM( "Unable to delete %s, it is not a leaf" % ent.dn) else: for entry in ents: self.log.warning("Warning: %s (%s)", entry.dn, ent.dn) self.conn.delete_s(ent.dn)
def _validate(self, rdn, properties, basedn): """Used to validate a create request. This way, it can be over-ridden without affecting the create types. It also checks that all the values in _must_attribute exist in some form in the dictionary. It has the useful trick of returning the dn, so subtypes can use extra properties to create the dn's here for this. """ if properties is None: raise ldap.UNWILLING_TO_PERFORM( 'Invalid request to create. Properties cannot be None') if type(properties) != dict: raise ldap.UNWILLING_TO_PERFORM("properties must be a dictionary") # I think this needs to be made case insensitive # How will this work with the dictionary? for attr in self._must_attributes: if properties.get(attr, None) is None: raise ldap.UNWILLING_TO_PERFORM( 'Attribute %s must not be None' % attr) # Make sure the naming attribute is present if properties.get(self._rdn_attribute, None) is None and rdn is None: raise ldap.UNWILLING_TO_PERFORM( 'Attribute %s must not be None or rdn provided' % self._rdn_attribute) # Great! Now, lets fix up our types for k, v in properties.items(): if isinstance(v, list): # Great! pass else: # Turn it into a list instead. properties[k] = [ v, ] # This change here, means we can pre-load a full dn to _dn, or we can # accept based on the rdn tdn = self._dn if tdn is None: if basedn is None: raise ldap.UNWILLING_TO_PERFORM( 'Invalid request to create. basedn cannot be None') if rdn is not None: tdn = ensure_str('%s,%s' % (rdn, basedn)) elif properties.get(self._rdn_attribute, None) is not None: # Favour the value in the properties dictionary v = properties.get(self._rdn_attribute) rdn = ensure_str(v[0]) erdn = ensure_str(ldap.dn.escape_dn_chars(rdn)) self._log.debug("Using first property %s: %s as rdn" % (self._rdn_attribute, erdn)) # Now we compare. If we changed this value, we have to put it back to make the properties complete. if erdn != rdn: properties[self._rdn_attribute].append(erdn) tdn = ensure_str('%s=%s,%s' % (self._rdn_attribute, erdn, basedn)) # We may need to map over the data in the properties dict to satisfy python-ldap str_props = {} for k, v in properties.items(): str_props[k] = ensure_list_bytes(v) # # Do we need to do extra dn validation here? return (tdn, str_props)
def create(self, suffix=None, properties=None): """ Creates backend entry and returns its dn. If the properties 'chain-bind-pwd' and 'chain-bind-dn' and 'chain-urls' are specified the backend is a chained backend. A chaining backend is created under 'cn=chaining database,cn=plugins,cn=config'. A local backend is created under 'cn=ldbm database,cn=plugins,cn=config' @param suffix - suffix stored in the backend @param properties - dictionary with properties values supported properties are BACKEND_NAME = 'name' BACKEND_READONLY = 'read-only' BACKEND_REQ_INDEX = 'require-index' BACKEND_CACHE_ENTRIES = 'entry-cache-number' BACKEND_CACHE_SIZE = 'entry-cache-size' BACKEND_DNCACHE_SIZE = 'dn-cache-size' BACKEND_DIRECTORY = 'directory' BACKEND_DB_DEADLOCK = 'db-deadlock' BACKEND_CHAIN_BIND_DN = 'chain-bind-dn' BACKEND_CHAIN_BIND_PW = 'chain-bind-pw' BACKEND_CHAIN_URLS = 'chain-urls' BACKEND_SUFFIX = 'suffix' BACKEND_SAMPLE_ENTRIES = 'sample_entries' @return backend DN of the created backend @raise LDAPError """ def _getBackendName(parent): ''' Use to build a backend name that is not already used ''' index = 1 while True: bename = "local%ddb" % index base = ("%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], bename, parent)) filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE self.log.debug("_getBackendName: baser=%s : fileter=%s", base, filt) try: self.conn.getEntry(base, ldap.SCOPE_BASE, filt) except (NoSuchEntryError, ldap.NO_SUCH_OBJECT): self.log.info("backend name will be %s", bename) return bename index += 1 # suffix is mandatory. If may be in the properties if isinstance(properties, dict) and properties.get( BACKEND_SUFFIX, None) is not None: suffix = properties.get(BACKEND_SUFFIX) if not suffix: raise ldap.UNWILLING_TO_PERFORM('Missing Suffix') else: nsuffix = normalizeDN(suffix) # Check it does not already exist a backend for that suffix if self.conn.verbose: self.log.info("Checking suffix %s for existence", suffix) ents = self.conn.backend.list(suffix=suffix) if len(ents) != 0: raise ldap.ALREADY_EXISTS # Check if we are creating a local/chained backend chained_suffix = (properties and (BACKEND_CHAIN_BIND_DN in properties) and (BACKEND_CHAIN_BIND_PW in properties) and (BACKEND_CHAIN_URLS in properties)) if chained_suffix: self.log.info("Creating a chaining backend") dnbase = DN_CHAIN else: self.log.info("Creating a local backend") dnbase = DN_LDBM # Get the future backend name if properties and BACKEND_NAME in properties: cn = properties[BACKEND_NAME] else: cn = _getBackendName(dnbase) # Check the future backend name does not already exists # we can imagine having no backends for 'suffix' but having a backend # with the same name dn = "%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], cn, dnbase) ents = self.conn.backend.list(backend_dn=dn) if ents: raise ldap.ALREADY_EXISTS( "Backend already exists with that DN: %s" % ents[0].dn) # All checks are done, Time to create the backend try: entry = Entry(dn) entry.update({ 'objectclass': ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME]: cn, BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]: nsuffix, }) if chained_suffix: entry.update({ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_URLS]: properties[BACKEND_CHAIN_URLS], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_DN]: properties[BACKEND_CHAIN_BIND_DN], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_PW]: properties[BACKEND_CHAIN_BIND_PW] }) self.log.debug("adding entry: %r", entry) self.conn.add_s(entry) except ldap.ALREADY_EXISTS as e: self.log.error("Entry already exists: %r", dn) raise ldap.ALREADY_EXISTS("%s : %r" % (e, dn)) except ldap.LDAPError as e: self.log.error("Could not add backend entry: %r", dn) raise e backend_entry = self.conn._test_entry(dn, ldap.SCOPE_BASE) return backend_entry
def delete(self, suffix=None, backend_dn=None, bename=None): """ Deletes the backend entry with the following steps: Delete the indexes entries under this backend Delete the encrypted attributes entries under this backend Delete the encrypted attributes keys entries under this backend If a mapping tree entry uses this backend (nsslapd-backend), it raise ldap.UNWILLING_TO_PERFORM If 'suffix'/'backend_dn'/'benamebase' are specified. It uses 'backend_dn' first, then 'suffix', then 'benamebase'. @param suffix - suffix of the backend @param backend_dn - DN of the backend entry @param bename - 'commonname'/'cn' of the backend (e.g. 'userRoot') @return None @raise ldap.UNWILLING_TO_PERFORM - if several backends match the argument provided suffix does not match backend suffix. It exists a mapping tree that use that backend """ # First check the backend exists and retrieved its suffix be_ents = self.conn.backend.list(suffix=suffix, backend_dn=backend_dn, bename=bename) if len(be_ents) == 0: raise ldap.UNWILLING_TO_PERFORM( "Unable to retrieve the backend (%r, %r, %r)" % (suffix, backend_dn, bename)) elif len(be_ents) > 1: for ent in be_ents: self.log.fatal("Multiple backend match the definition: %s", ent.dn) if (not suffix) and (not backend_dn) and (not bename): raise ldap.UNWILLING_TO_PERFORM( "suffix and backend DN and backend name are missing") raise ldap.UNWILLING_TO_PERFORM( "Not able to identify the backend to delete") else: be_ent = be_ents[0] be_suffix = be_ent.getValue( BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]) # Verify the provided suffix is the one stored in the found backend if suffix: if normalizeDN(suffix) != normalizeDN(be_suffix): raise ldap.UNWILLING_TO_PERFORM( "provided suffix (%s) differs from backend suffix (%s)" % (suffix, be_suffix)) # now check there is no mapping tree for that suffix mt_ents = self.conn.mappingtree.list(suffix=be_suffix) if len(mt_ents) > 0: raise ldap.UNWILLING_TO_PERFORM( "It still exists a mapping tree (%s) for that backend (%s)" % (mt_ents[0].dn, be_ent.dn)) # Now delete the indexes found_bename = ensure_str( be_ent.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME])) if not bename: bename = found_bename elif bename.lower() != found_bename.lower(): raise ldap.UNWILLING_TO_PERFORM( "Backend name specified (%s) differs from the retrieved one (%s)" % (bename, found_bename)) self.conn.index.delete_all(bename) # finally delete the backend children and the backend itself ents = self.conn.search_s(be_ent.dn, ldap.SCOPE_ONELEVEL) for ent in ents: self.log.debug("Delete entry children %s", ent.dn) self.conn.delete_s(ent.dn) self.log.debug("Delete backend entry %s", be_ent.dn) self.conn.delete_s(be_ent.dn) return