class ChainingLink(DSLdapObject): """Chaining Backend DSLdapObject with: - must attributes = ['cn', 'nsslapd-suffix', 'nsmultiplexorbinddn', 'nsmultiplexorcredentials', 'nsfarmserverurl' - RDN attribute is 'cn' :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str """ _must_attributes = ['nsslapd-suffix', 'cn'] def __init__(self, instance, dn=None, rdn=None): super(ChainingLink, self).__init__(instance, dn) self._rdn_attribute = 'cn' self._must_attributes = [ 'nsslapd-suffix', 'cn', 'nsmultiplexorbinddn', 'nsmultiplexorcredentials', 'nsfarmserverurl' ] self._create_objectclasses = [ 'top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE ] self._protected = False self._basedn = "cn=chaining database,cn=plugins,cn=config" self._mts = MappingTrees(self._instance) def get_monitor(self): """Get a MonitorChaining(DSLdapObject) for the chaining link :returns - chaining monitor entry """ return MonitorChaining(instance=self._instance, dn="cn=monitor,%s" % self._dn) def del_link(self): """ Remove the link from the parent suffix backend entry Delete chaining monitor entry Delete chaining entry """ rdn = self.get_attr_val_utf8_l('nsslapd-suffix') try: mt = self._mts.get(selector=rdn) mt.delete() except ldap.NO_SUCH_OBJECT: # Righto, it's already gone! Do nothing ... pass # Delete the monitoring entry monitor = self.get_monitor() monitor.delete() # Delete the link self.delete() def create(self, rdn=None, properties=None, basedn=None): """Create the link entry, and the mapping tree entry(if needed) """ # Create chaining entry super(ChainingLink, self).create(rdn, properties, basedn) # Create mapping tree entry dn_comps = ldap.explode_dn(properties['nsslapd-suffix'][0]) parent_suffix = ','.join(dn_comps[1:]) mt_properties = { 'cn': properties['nsslapd-suffix'][0], 'nsslapd-state': 'backend', 'nsslapd-backend': properties['cn'][0], 'nsslapd-parent-suffix': parent_suffix } try: self._mts.ensure_state(properties=mt_properties) except ldap.ALREADY_EXISTS: pass
class Backend(DSLdapObject): """Backend DSLdapObject with: - must attributes = ['cn', 'nsslapd-suffix'] - RDN attribute is 'cn' :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str """ _must_attributes = ['nsslapd-suffix', 'cn'] def __init__(self, instance, dn=None): super(Backend, self).__init__(instance, dn) self._rdn_attribute = 'cn' self._must_attributes = ['nsslapd-suffix', 'cn'] self._create_objectclasses = [ 'top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE ] self._protected = False # Check if a mapping tree for this suffix exists. self._mts = MappingTrees(self._instance) def lint_uid(self): return self.get_attr_val_utf8_l('cn').lower() def _lint_virt_attrs(self): """Check if any virtual attribute are incorrectly indexed""" bename = self.lint_uid() indexes = self.get_indexes() suffix = self.get_attr_val_utf8('nsslapd-suffix') # First check nsrole try: indexes.get('nsrole') report = copy.deepcopy(DSVIRTLE0001) report['check'] = f'backends:{bename}:virt_attrs' report['detail'] = report['detail'].replace('ATTR', 'nsrole') report['fix'] = report['fix'].replace('ATTR', 'nsrole') report['fix'] = report['fix'].replace('SUFFIX', suffix) report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid) report['items'].append(suffix) report['items'].append('nsrole') yield report except: pass # Check COS next for cosDefType in [ CosIndirectDefinitions, CosPointerDefinitions, CosClassicDefinitions ]: defs = cosDefType(self._instance, suffix).list() for cosDef in defs: attrs = cosDef.get_attr_val_utf8_l("cosAttribute").split() for attr in attrs: if attr in [ "default", "override", "operational", "operational-default", "merge-schemes" ]: # We are at the end, just break out break try: indexes.get(attr) # If we got here there is an index (bad) report = copy.deepcopy(DSVIRTLE0001) report['check'] = f'backends:{bename}:virt_attrs' report['detail'] = report['detail'].replace( 'ATTR', attr) report['fix'] = report['fix'].replace('ATTR', attr) report['fix'] = report['fix'].replace('SUFFIX', suffix) report['fix'] = report['fix'].replace( 'YOUR_INSTANCE', self._instance.serverid) report['items'].append(suffix) report['items'].append("Class Of Service (COS)") report['items'].append("cosAttribute: " + attr) yield report except: # this is what we hope for pass def _lint_search(self): """Perform a search and make sure an entry is accessible """ dn = self.get_attr_val_utf8('nsslapd-suffix') bename = self.lint_uid() suffix = DSLdapObject(self._instance, dn=dn) try: suffix.get_attr_val('objectclass') except ldap.NO_SUCH_OBJECT: # backend root entry not created yet DSBLE0003['items'] = [ dn, ] DSBLE0003['check'] = f'backends:{bename}:search' yield DSBLE0003 except ldap.LDAPError as e: # Some other error DSBLE0002['detail'] = DSBLE0002['detail'].replace('ERROR', str(e)) DSBLE0002['check'] = f'backends:{bename}:search' DSBLE0002['items'] = [ dn, ] yield DSBLE0002 def _lint_mappingtree(self): """Backend lint This should check for: * missing mapping tree entries for the backend * missing indices if we are local and have log access? """ # Check for the missing mapping tree. suffix = self.get_attr_val_utf8('nsslapd-suffix') bename = self.lint_uid() try: mt = self._mts.get(suffix) if mt.get_attr_val_utf8( 'nsslapd-backend') != bename and mt.get_attr_val_utf8( 'nsslapd-state') != 'backend': raise ldap.NO_SUCH_OBJECT( "We have a matching suffix, but not a backend or correct database name." ) except ldap.NO_SUCH_OBJECT: result = DSBLE0001 result['check'] = f'backends:{bename}:mappingtree' result['items'] = [ bename, ] yield result def _lint_cl_trimming(self): """Check that cl trimming is at least defined to prevent unbounded growth""" bename = self.lint_uid() suffix = self.get_attr_val_utf8('nsslapd-suffix') replicas = Replicas(self._instance) try: # Check if replication is enabled replicas.get(suffix) # Check the changelog cl = Changelog(self._instance, suffix=suffix) if cl.get_attr_val_utf8('nsslapd-changelogmaxentries') is None and \ cl.get_attr_val_utf8('nsslapd-changelogmaxage') is None: report = copy.deepcopy(DSCLLE0001) report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid) report['check'] = f'backends:{bename}::cl_trimming' yield report except: # Suffix is not replicated self._log.debug( f"_lint_cl_trimming - backend ({suffix}) is not replicated") pass def create_sample_entries(self, version): """Creates sample entries under nsslapd-suffix value :param version: Sample entries version, i.e. 001003006 :type version: str """ self._log.debug('Requested sample entries at version %s....' % version) # Grab the correct sample entry config - remember this is a function ptr. centries = get_sample_entries(version) # apply it. basedn = self.get_attr_val('nsslapd-suffix') cent = centries(self._instance, basedn) # Now it's built, we can get the version for logging. self._log.debug('Creating sample entries at version %s' % cent.version) cent.apply() 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 create(self, dn=None, properties=None, basedn=DN_LDBM, create_mapping_tree=True): """Add a new backend entry, create mapping tree, and, if requested, sample entries :param dn: DN of the new entry :type dn: str :param properties: Attributes and parameters for the new entry :type properties: dict :param basedn: Base DN of the new entry :type basedn: str :param create_mapping_tree: If a related mapping tree node should be created :type create_mapping_tree: bool :returns: DSLdapObject of the created entry """ sample_entries = False parent_suffix = False # normalize suffix (remove spaces between comps) if dn is not None: dn_comps = ldap.dn.explode_dn(dn.lower()) dn = ",".join(dn_comps) if properties is not None: suffix_dn = properties['nsslapd-suffix'].lower() dn_comps = ldap.dn.explode_dn(suffix_dn) ndn = ",".join(dn_comps) properties['nsslapd-suffix'] = ndn sample_entries = properties.pop(BACKEND_SAMPLE_ENTRIES, False) parent_suffix = properties.pop('parent', False) # Okay, now try to make the backend. super(Backend, self).create(dn, properties, basedn) # We check if the mapping tree exists in create, so do this *after* if create_mapping_tree is True: properties = { 'cn': self._nprops_stash['nsslapd-suffix'], 'nsslapd-state': 'backend', 'nsslapd-backend': self._nprops_stash['cn'], } if parent_suffix: # This is a subsuffix, set the parent suffix properties['nsslapd-parent-suffix'] = parent_suffix self._mts.create(properties=properties) # We can't create the sample entries unless a mapping tree was installed. if sample_entries is not False and create_mapping_tree is True: self.create_sample_entries(sample_entries) return self 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 get_suffix(self): return self.get_attr_val_utf8_l('nsslapd-suffix') def disable(self): # Disable backend (mapping tree) suffix = self.get_attr_val_utf8_l('nsslapd-suffix') mt = self._mts.get(suffix) mt.set('nsslapd-nsstate', 'Disabled') def enable(self): # Enable Backend (mapping tree) suffix = self.get_attr_val_utf8_l('nsslapd-suffix') mt = self._mts.get(suffix) mt.set('nsslapd-nsstate', 'Backend') def get_mapping_tree(self): suffix = self.get_attr_val_utf8('nsslapd-suffix') return self._mts.get(suffix) def get_monitor(self): """Get a MonitorBackend(DSLdapObject) for the backend""" # We need to be a factor to the backend monitor from lib389.monitor import MonitorBackend monitor = MonitorBackend(instance=self._instance, dn="cn=monitor,%s" % self._dn) return monitor def get_indexes(self): """Get an Indexes(DSLdapObject) for the backend""" indexes = Indexes(self._instance, basedn="cn=index,%s" % self._dn) return indexes def get_index(self, attr_name): for index in self.get_indexes().list(): idx_name = index.get_attr_val_utf8_l('cn').lower() if idx_name == attr_name.lower(): return index return None def del_index(self, attr_name): for index in self.get_indexes().list(): idx_name = index.get_attr_val_utf8_l('cn').lower() if idx_name == attr_name.lower(): index.delete() return raise ValueError("Can not delete index because it does not exist") def add_index(self, attr_name, types, matching_rules=None, reindex=False): """ Add an index. :param attr_name - name of the attribute to index :param types - a List of index types(eq, pres, sub, approx) :param matching_rules - a List of matching rules for the index :param reindex - If set to True then index the attribute after creating it. """ new_index = Index(self._instance) props = { 'cn': attr_name, 'nsSystemIndex': 'False', 'nsIndexType': types, } if matching_rules is not None: mrs = [] for mr in matching_rules: mrs.append(mr) # Only add if there are actually rules present in the list. if len(mrs) > 0: props['nsMatchingRule'] = mrs new_index.create(properties=props, basedn="cn=index," + self._dn) if reindex: self.reindex(attr_name) def reindex(self, attrs=None, wait=False): """Reindex the attributes for this backend :param attrs - an optional list of attributes to index :param wait - Set to true to wait for task to complete """ args = None if wait: args = {TASK_WAIT: True} bename = ensure_str(self.get_attr_val_bytes('cn')) reindex_task = Tasks(self._instance) reindex_task.reindex(benamebase=bename, attrname=attrs, args=args) def get_encrypted_attrs(self, just_names=False): """Get a list of the excrypted attributes :param just_names - If True only the encrypted attribute names are returned (instead of the full attribute entry) :returns - a list of attributes """ attrs = EncryptedAttrs(self._instance, basedn=self._dn).list() if just_names: results = [] for attr in attrs: results.append(attr.get_attr_val_utf8_l('cn')) return results else: return attrs def add_encrypted_attr(self, attr_name): """Add an encrypted attribute :param attr_name - name of the new encrypted attribute """ new_attr = EncryptedAttr(self._instance) new_attr.create(basedn="cn=encrypted attributes," + self._dn, properties={ 'cn': attr_name, 'nsEncryptionAlgorithm': 'AES' }) def del_encrypted_attr(self, attr_name): """Delete encrypted attribute :param attr_name - Name of the encrypted attribute to delete """ enc_attrs = EncryptedAttrs(self._instance, basedn="cn=encrypted attributes," + self._dn).list() for enc_attr in enc_attrs: attr = enc_attr.get_attr_val_utf8_l('cn').lower() if attr_name == attr.lower(): enc_attr.delete() break def import_ldif(self, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=None, only_core=False, include_suffixes=None, exclude_suffixes=None): """Do an import of the suffix""" bs = Backends(self._instance) task = bs.import_ldif(self.rdn, ldifs, chunk_size, encrypted, gen_uniq_id, only_core, include_suffixes, exclude_suffixes) return task def export_ldif(self, ldif=None, use_id2entry=False, encrypted=False, min_base64=False, no_uniq_id=False, replication=False, not_folded=False, no_seq_num=False, include_suffixes=None, exclude_suffixes=None): """Do an export of the suffix""" bs = Backends(self._instance) task = bs.export_ldif(self.rdn, ldif, use_id2entry, encrypted, min_base64, no_uniq_id, replication, not_folded, no_seq_num, include_suffixes, exclude_suffixes) return task def get_vlv_searches(self, vlv_name=None): """Return the VLV seaches for this backend, or return a specific search :param vlv_name - name of a VLV search entry to return :returns - A list of VLV searches or a single VLV sarch entry """ vlv_searches = VLVSearches(self._instance, basedn=self._dn).list() if vlv_name is None: return vlv_searches # return specific search for vlv in vlv_searches: search_name = vlv.get_attr_val_utf8_l('cn').lower() if search_name == vlv_name.lower(): return vlv # No match raise ValueError("Failed to find VLV search entry") def add_vlv_search(self, vlvname, props, reindex=False): """Add a VLV search entry :param: vlvname - Name of the new VLV search entry :props - A dict of the attribute value pairs for the VLV search entry :param - reindex - Set to True to index the new attribute right away """ basedn = self._dn vlv = VLVSearch(instance=self._instance) vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn) def get_sub_suffixes(self): """Return a list of Backend's returns: a List of subsuffix entries """ subsuffixes = [] top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix').lower() mts = self._mts.list() for mt in mts: parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix') if parent_suffix is None: continue if parent_suffix.lower() == top_be_suffix: child_suffix = mt.get_attr_val_utf8_l('cn').lower() be_insts = Backends(self._instance).list() for be in be_insts: be_suffix = ensure_str( be.get_attr_val_utf8_l('nsslapd-suffix')).lower() if child_suffix == be_suffix: subsuffixes.append(be) break return subsuffixes def get_cos_indirect_defs(self): return CosIndirectDefinitions(self._instance, self._dn).list() def get_cos_pointer_defs(self): return CosPointerDefinitions(self._instance, self._dn).list() def get_cos_classic_defs(self): return CosClassicDefinitions(self._instance, self._dn).list() def get_cos_templates(self): return CosTemplates(self._instance, self._dn).list() def get_state(self): suffix = self.get_attr_val_utf8('nsslapd-suffix') try: mt = self._mts.get(suffix) except ldap.NO_SUCH_OBJECT: raise ValueError( "Backend missing mapping tree entry, unable to get state") return mt.get_attr_val_utf8('nsslapd-state') def set_state(self, new_state): new_state = new_state.lower() suffix = self.get_attr_val_utf8('nsslapd-suffix') try: mt = self._mts.get(suffix) except ldap.NO_SUCH_OBJECT: raise ValueError( "Backend missing mapping tree entry, unable to set configuration" ) if new_state not in [ 'backend', 'disabled', 'referral', 'referral on update' ]: raise ValueError( f"Invalid backend state {new_state}, value must be one of the following: 'backend', 'disabled', 'referral', 'referral on update'" ) # Can not change state of replicated backend replicas = Replicas(self._instance) try: # Check if replication is enabled replicas.get(suffix) raise ValueError( "Can not change the backend state of a replicated suffix") except ldap.NO_SUCH_OBJECT: pass # Ok, change the state mt.replace('nsslapd-state', new_state)
class Backend(DSLdapObject): """Backend DSLdapObject with: - must attributes = ['cn', 'nsslapd-suffix'] - RDN attribute is 'cn' :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str """ _must_attributes = ['nsslapd-suffix', 'cn'] def __init__(self, instance, dn=None): super(Backend, self).__init__(instance, dn) self._rdn_attribute = 'cn' self._must_attributes = ['nsslapd-suffix', 'cn'] self._create_objectclasses = [ 'top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE ] self._protected = False self._lint_functions = [self._lint_mappingtree] # Check if a mapping tree for this suffix exists. self._mts = MappingTrees(self._instance) def create_sample_entries(self, version): """Creates sample entries under nsslapd-suffix value :param version: Sample entries version, i.e. 001003006 :type version: str """ self._log.debug('Creating sample entries at version %s....' % version) # Grab the correct sample entry config centries = get_sample_entries(version) # apply it. basedn = self.get_attr_val('nsslapd-suffix') cent = centries(self._instance, basedn) cent.apply() 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 create(self, dn=None, properties=None, basedn=None): """Add a new backend entry, create mapping tree, and, if requested, sample entries :param rdn: RDN of the new entry :type rdn: str :param properties: Attributes and parameters for the new entry :type properties: dict :param basedn: Base DN of the new entry :type rdn: str :returns: DSLdapObject of the created entry """ sample_entries = properties.pop(BACKEND_SAMPLE_ENTRIES, False) # Okay, now try to make the backend. super(Backend, self).create(dn, properties, basedn) # We check if the mapping tree exists in create, so do this *after* self._mts.create( properties={ 'cn': self._nprops_stash['nsslapd-suffix'], 'nsslapd-state': 'backend', 'nsslapd-backend': self._nprops_stash['cn'], }) if sample_entries is not False: self.create_sample_entries(sample_entries) return self 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') != 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 _lint_mappingtree(self): """Backend lint This should check for: * missing mapping tree entries for the backend * missing indices if we are local and have log access? """ # Check for the missing mapping tree. suffix = self.get_attr_val_utf8('nsslapd-suffix') bename = self.get_attr_val_bytes('cn') try: mt = self._mts.get(suffix) if mt.get_attr_val_bytes( 'nsslapd-backend') != bename and mt.get_attr_val( 'nsslapd-state') != ensure_bytes('backend'): raise ldap.NO_SUCH_OBJECT( "We have a matching suffix, but not a backend or correct database name." ) except ldap.NO_SUCH_OBJECT: result = DSBLE0001 result['items'] = [ bename, ] return result return None def get_mapping_tree(self): suffix = self.get_attr_val_utf8('nsslapd-suffix') return self._mts.get(suffix) def get_monitor(self): """Get a MonitorBackend(DSLdapObject) for the backend""" monitor = MonitorBackend(instance=self._instance, dn="cn=monitor,%s" % self._dn) return monitor def get_indexes(self): """Get an Indexes(DSLdapObject) for the backend""" indexes = Indexes(self._instance, basedn="cn=index,%s" % self._dn) return indexes
class Backend(DSLdapObject): """Backend DSLdapObject with: - must attributes = ['cn', 'nsslapd-suffix'] - RDN attribute is 'cn' :param instance: An instance :type instance: lib389.DirSrv :param dn: Entry DN :type dn: str """ _must_attributes = ['nsslapd-suffix', 'cn'] def __init__(self, instance, dn=None): super(Backend, self).__init__(instance, dn) self._rdn_attribute = 'cn' self._must_attributes = ['nsslapd-suffix', 'cn'] self._create_objectclasses = ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE] self._protected = False self._lint_functions = [self._lint_mappingtree] # Check if a mapping tree for this suffix exists. self._mts = MappingTrees(self._instance) def create_sample_entries(self, version): """Creates sample entries under nsslapd-suffix value :param version: Sample entries version, i.e. 001003006 :type version: str """ self._log.debug('Creating sample entries at version %s....' % version) # Grab the correct sample entry config centries = get_sample_entries(version) # apply it. basedn = self.get_attr_val('nsslapd-suffix') cent = centries(self._instance, basedn) cent.apply() 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 create(self, dn=None, properties=None, basedn=DN_LDBM): """Add a new backend entry, create mapping tree, and, if requested, sample entries :param dn: DN of the new entry :type dn: str :param properties: Attributes and parameters for the new entry :type properties: dict :param basedn: Base DN of the new entry :type basedn: str :returns: DSLdapObject of the created entry """ sample_entries = False parent_suffix = False # normalize suffix (remove spaces between comps) if dn is not None: dn_comps = ldap.dn.explode_dn(dn.lower()) dn = ",".join(dn_comps) if properties is not None: suffix_dn = properties['nsslapd-suffix'].lower() dn_comps = ldap.dn.explode_dn(suffix_dn) ndn = ",".join(dn_comps) properties['nsslapd-suffix'] = ndn sample_entries = properties.pop(BACKEND_SAMPLE_ENTRIES, False) parent_suffix = properties.pop('parent', False) # Okay, now try to make the backend. super(Backend, self).create(dn, properties, basedn) # We check if the mapping tree exists in create, so do this *after* properties = { 'cn': self._nprops_stash['nsslapd-suffix'], 'nsslapd-state': 'backend', 'nsslapd-backend': self._nprops_stash['cn'], } if parent_suffix: # This is a subsuffix, set the parent suffix properties['nsslapd-parent-suffix'] = parent_suffix self._mts.create(properties=properties) if sample_entries is not False: self.create_sample_entries(sample_entries) return self 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 _lint_mappingtree(self): """Backend lint This should check for: * missing mapping tree entries for the backend * missing indices if we are local and have log access? """ # Check for the missing mapping tree. suffix = self.get_attr_val_utf8('nsslapd-suffix') bename = self.get_attr_val_bytes('cn') try: mt = self._mts.get(suffix) if mt.get_attr_val_bytes('nsslapd-backend') != bename and mt.get_attr_val('nsslapd-state') != ensure_bytes('backend'): raise ldap.NO_SUCH_OBJECT("We have a matching suffix, but not a backend or correct database name.") except ldap.NO_SUCH_OBJECT: result = DSBLE0001 result['items'] = [bename, ] return result return None def get_suffix(self): return self.get_attr_val_utf8_l('nsslapd-suffix') def disable(self): # Disable backend (mapping tree) suffix = self.get_attr_val_utf8_l('nsslapd-suffix') mt = self._mts.get(suffix) mt.set('nsslapd-nsstate', 'Disabled') def enable(self): # Enable Backend (mapping tree) suffix = self.get_attr_val_utf8_l('nsslapd-suffix') mt = self._mts.get(suffix) mt.set('nsslapd-nsstate', 'Backend') def get_mapping_tree(self): suffix = self.get_attr_val_utf8('nsslapd-suffix') return self._mts.get(suffix) def get_monitor(self): """Get a MonitorBackend(DSLdapObject) for the backend""" monitor = MonitorBackend(instance=self._instance, dn="cn=monitor,%s" % self._dn) return monitor def get_indexes(self): """Get an Indexes(DSLdapObject) for the backend""" indexes = Indexes(self._instance, basedn="cn=index,%s" % self._dn) return indexes def get_index(self, attr_name): for index in self.get_indexes().list(): idx_name = index.get_attr_val_utf8_l('cn').lower() if idx_name == attr_name.lower(): return index return None def del_index(self, attr_name): for index in self.get_indexes().list(): idx_name = index.get_attr_val_utf8_l('cn').lower() if idx_name == attr_name.lower(): index.delete() return raise ValueError("Can not delete index because it does not exist") def add_index(self, attr_name, types, matching_rules=[], reindex=False): """ Add an index. :param attr_name - name of the attribute to index :param types - a List of index types(eq, pres, sub, approx) :param matching_rules - a List of matching rules for the index :param reindex - If set to True then index the attribute after creating it. """ new_index = Index(self._instance) props = {'cn': attr_name, 'nsSystemIndex': 'False', 'nsIndexType': types, } if matching_rules is not None: mrs = [] for mr in matching_rules: mrs.append(mr) props['nsMatchingRule'] = mrs new_index.create(properties=props, basedn="cn=index," + self._dn) if reindex: self.reindex(attr_name) def reindex(self, attrs=None, wait=False): """Reindex the attributes for this backend :param attrs - an optional list of attributes to index :param wait - Set to true to wait for task to complete """ args = None if wait: args = {TASK_WAIT: True} bename = ensure_str(self.get_attr_val_bytes('cn')) reindex_task = Tasks(self._instance) reindex_task.reindex(benamebase=bename, attrname=attrs, args=args) def get_encrypted_attrs(self, just_names=False): """Get a list of the excrypted attributes :param just_names - If True only the encrypted attribute names are returned (instead of the full attribute entry) :returns - a list of attributes """ attrs = EncryptedAttrs(self._instance, basedn=self._dn).list() if just_names: results = [] for attr in attrs: results.append(attr.get_attr_val_utf8_l('cn')) return results else: return attrs def add_encrypted_attr(self, attr_name): """Add an encrypted attribute :param attr_name - name of the new encrypted attribute """ new_attr = EncryptedAttr(self._instance) new_attr.create(basedn="cn=encrypted attributes," + self._dn, properties={'cn': attr_name,'nsEncryptionAlgorithm': 'AES'}) def del_encrypted_attr(self, attr_name): """Delete encrypted attribute :param attr_name - Name of the encrypted attribute to delete """ enc_attrs = EncryptedAttrs(self._instance, basedn="cn=encrypted attributes," + self._dn).list() for enc_attr in enc_attrs: attr = enc_attr.get_attr_val_utf8_l('cn').lower() if attr_name == attr.lower(): enc_attr.delete() break def import_ldif(self, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=False, only_core=False, include_suffixes=None, exclude_suffixes=None): """Do an import of the suffix""" bs = Backends(self._instance) task = bs.import_ldif(self.rdn, ldifs, chunk_size, encrypted, gen_uniq_id, only_core, include_suffixes, exclude_suffixes) return task def export_ldif(self, ldif=None, use_id2entry=False, encrypted=False, min_base64=False, no_uniq_id=False, replication=False, not_folded=False, no_seq_num=False, include_suffixes=None, exclude_suffixes=None): """Do an export of the suffix""" bs = Backends(self._instance) task = bs.export_ldif(self.rdn, ldif, use_id2entry, encrypted, min_base64, no_uniq_id, replication, not_folded, no_seq_num, include_suffixes, exclude_suffixes) return task def get_vlv_searches(self, vlv_name=None): """Return the VLV seaches for this backend, or return a specific search :param vlv_name - name of a VLV search entry to return :returns - A list of VLV searches or a single VLV sarch entry """ vlv_searches = VLVSearches(self._instance, basedn=self._dn).list() if vlv_name is None: return vlv_searches # return specific search for vlv in vlv_searches: search_name = vlv.get_attr_val_utf8_l('cn').lower() if search_name == vlv_name.lower(): return vlv # No match raise ValueError("Failed to find VLV search entry") def add_vlv_search(self, vlvname, props, reindex=False): """Add a VLV search entry :param: vlvname - Name of the new VLV search entry :props - A dict of the attribute value pairs for the VLV search entry :param - reindex - Set to True to index the new attribute right away """ basedn = self._dn vlv = VLVSearch(instance=self._instance) vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn) def get_sub_suffixes(self): """Return a list of Backend's returns: a List of subsuffix entries """ subsuffixes = [] top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix').lower() mts = self._mts.list() for mt in mts: parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix') if parent_suffix is None: continue if parent_suffix.lower() == top_be_suffix: child_suffix = mt.get_attr_val_utf8_l('cn').lower() be_insts = Backends(self._instance).list() for be in be_insts: be_suffix = ensure_str(be.get_attr_val_utf8_l('nsslapd-suffix')).lower() if child_suffix == be_suffix: subsuffixes.append(be) break return subsuffixes
def test_mapping_tree_mixed_length(topology): inst = topology.standalone # First create two Backends, without mapping trees. be1 = create_backend(inst, 'userRootA', 'dc=myserver') be1 = create_backend(inst, 'userRootB', 'dc=m') be1 = create_backend(inst, 'userRootC', 'dc=a,dc=b,dc=c,dc=d,dc=e') be1 = create_backend(inst, 'userRootD', 'dc=example,dc=com') be1 = create_backend(inst, 'userRootE', 'dc=myldap') mts = MappingTrees(inst) mts.create( properties={ 'cn': 'dc=myserver', 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRootA', }) mts.create(properties={ 'cn': 'dc=m', 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRootB', }) mts.create( properties={ 'cn': 'dc=a,dc=b,dc=c,dc=d,dc=e', 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRootC', }) mts.create( properties={ 'cn': 'dc=example,dc=com', 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRootD', }) mts.create( properties={ 'cn': 'dc=myldap', 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRootE', }) dc_a = Domain(inst, dn='dc=myserver') assert dc_a.exists() dc_b = Domain(inst, dn='dc=m') assert dc_b.exists() dc_c = Domain(inst, dn='dc=a,dc=b,dc=c,dc=d,dc=e') assert dc_c.exists() dc_d = Domain(inst, dn='dc=example,dc=com') assert dc_d.exists() dc_e = Domain(inst, dn='dc=myldap') assert dc_e.exists() inst.restart() assert dc_a.exists() assert dc_b.exists() assert dc_c.exists() assert dc_d.exists() assert dc_e.exists()
def test_healthcheck_backend_missing_mapping_tree(topology_st): """Check if HealthCheck returns DSBLE0001 and DSBLE0003 code :id: 4c83ffcf-01a4-4ec8-a3d2-01022b566225 :setup: Standalone instance :steps: 1. Create DS instance 2. Disable the dc=example,dc=com backend suffix entry in the mapping tree 3. Use HealthCheck without --json option 4. Use HealthCheck with --json option 5. Enable the dc=example,dc=com backend suffix entry in the mapping tree 6. Use HealthCheck without --json option 7. Use HealthCheck with --json option :expectedresults: 1. Success 2. Success 3. Healthcheck reports DSBLE0001 and DSBLE0003 codes and related details 4. Healthcheck reports DSBLE0001 and DSBLE0003 codes and related details 5. Success 6. Healthcheck reports no issue found 7. Healthcheck reports no issue found """ RET_CODE1 = 'DSBLE0001' RET_CODE2 = 'DSBLE0003' standalone = topology_st.standalone log.info( 'Delete the dc=example,dc=com backend suffix entry in the mapping tree' ) mts = MappingTrees(standalone) mt = mts.get(DEFAULT_SUFFIX) mt.delete() run_healthcheck_and_flush_log(topology_st, standalone, RET_CODE1, json=False, searched_code2=RET_CODE2) run_healthcheck_and_flush_log(topology_st, standalone, RET_CODE1, json=True, searched_code2=RET_CODE2) log.info('Create the dc=example,dc=com backend suffix entry') mts.create( properties={ 'cn': DEFAULT_SUFFIX, 'nsslapd-state': 'backend', 'nsslapd-backend': 'userRoot', }) run_healthcheck_and_flush_log(topology_st, standalone, CMD_OUTPUT, json=False) run_healthcheck_and_flush_log(topology_st, standalone, JSON_OUTPUT, json=True)
def status(self): """Check if account is locked by Account Policy plugin or nsAccountLock (directly or indirectly) :returns: a dict in a format - {"status": status, "params": activity_data, "calc_time": epoch_time} """ inst = self._instance # Fetch Account Policy data if its enabled plugin = AccountPolicyPlugin(inst) try: config_dn = plugin.get_attr_val_utf8("nsslapd-pluginarg0") except IndexError: self._log.debug( "The bound user doesn't have rights to access Account Policy settings. Not checking." ) state_attr = "" alt_state_attr = "" limit = "" spec_attr = "" limit_attr = "" process_account_policy = False mapping_trees = MappingTrees(inst) try: root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn) if str.lower(root_suffix) == str.lower(self.dn): raise ValueError( "Root suffix can't be locked or unlocked via dsidm functionality." ) except ldap.NO_SUCH_OBJECT: self._log.debug( "Can't acquire root suffix from user DN. Probably - insufficient rights. Skipping this step." ) try: process_account_policy = plugin.status() except IndexError: pass if process_account_policy and config_dn is not None: config = AccountPolicyConfig(inst, config_dn) config_settings = config.get_attrs_vals_utf8([ "stateattrname", "altstateattrname", "specattrname", "limitattrname" ]) state_attr = self._dict_get_with_ignore_indexerror( config_settings, "stateattrname") alt_state_attr = self._dict_get_with_ignore_indexerror( config_settings, "altstateattrname") spec_attr = self._dict_get_with_ignore_indexerror( config_settings, "specattrname") limit_attr = self._dict_get_with_ignore_indexerror( config_settings, "limitattrname") mapping_trees = MappingTrees(inst) root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn) cos_entries = CosTemplates(inst, root_suffix) accpol_entry_dn = "" for cos in cos_entries.list(): if cos.present(spec_attr): accpol_entry_dn = cos.get_attr_val_utf8_l(spec_attr) if accpol_entry_dn: accpol_entry = AccountPolicyEntry(inst, accpol_entry_dn) else: accpol_entry = config limit = accpol_entry.get_attr_val_utf8_l(limit_attr) # Fetch account data account_data = self.get_attrs_vals_utf8([ "createTimestamp", "modifyTimeStamp", "nsAccountLock", state_attr ]) last_login_time = self._dict_get_with_ignore_indexerror( account_data, state_attr) if not last_login_time: last_login_time = self._dict_get_with_ignore_indexerror( account_data, alt_state_attr) create_time = self._dict_get_with_ignore_indexerror( account_data, "createTimestamp") modify_time = self._dict_get_with_ignore_indexerror( account_data, "modifyTimeStamp") acct_roles = self.get_attr_vals_utf8_l("nsRole") mapping_trees = MappingTrees(inst) root_suffix = "" try: root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn) except ldap.NO_SUCH_OBJECT: self._log.debug( "The bound user doesn't have rights to access disabled roles settings. Not checking." ) if root_suffix: roles = Roles(inst, root_suffix) try: disabled_roles = roles.get_disabled_roles() # Locked indirectly through a role locked_indirectly_role_dn = "" for role in acct_roles: if str.lower(role) in [ str.lower(role.dn) for role in disabled_roles.keys() ]: locked_indirectly_role_dn = role if locked_indirectly_role_dn: return self._format_status_message( AccountState.INDIRECTLY_LOCKED, create_time, modify_time, last_login_time, limit, locked_indirectly_role_dn) except ldap.NO_SUCH_OBJECT: pass # Locked directly if self._dict_get_with_ignore_indexerror(account_data, "nsAccountLock") == "true": return self._format_status_message(AccountState.DIRECTLY_LOCKED, create_time, modify_time, last_login_time, limit) # Locked indirectly through Account Policy plugin if process_account_policy and last_login_time: # Now check the Account Policy Plugin inactivity limits remaining_time = float(limit) - (time.mktime( time.gmtime()) - gentime_to_posix_time(last_login_time)) if remaining_time <= 0: return self._format_status_message( AccountState.INACTIVITY_LIMIT_EXCEEDED, create_time, modify_time, last_login_time, limit) # All checks are passed - we are active return self._format_status_message(AccountState.ACTIVATED, create_time, modify_time, last_login_time, limit)