예제 #1
0
def test_invalid_mt(topo):
    """Test that you can not add a new suffix/mapping tree
    that does not already have the backend entry created.

    :id: caabd407-f541-4695-b13f-8f92af1112a0
    :setup: Standalone Instance
    :steps:
        1. Create a new suffix that specifies an existing backend which has a
           different suffix.
        2. Create a suffix that has no backend entry at all.
    :expectedresults:
        1. Should fail with UNWILLING_TO_PERFORM
        1. Should fail with UNWILLING_TO_PERFORM
    """

    bad_suffix = 'dc=does,dc=not,dc=exist'
    mts = MappingTrees(topo.standalone)

    properties = {
        'cn': bad_suffix,
        'nsslapd-state': 'backend',
        'nsslapd-backend': 'userroot',
    }
    with pytest.raises(ldap.UNWILLING_TO_PERFORM):
        mts.create(properties=properties)

    properties = {
        'cn': bad_suffix,
        'nsslapd-state': 'backend',
        'nsslapd-backend': 'notCreatedRoot',
    }
    with pytest.raises(ldap.UNWILLING_TO_PERFORM):
        mts.create(properties=properties)
예제 #2
0
def test_mapping_tree_flipped_components(topology):
    inst = topology.standalone
    # First create two Backends, without mapping trees.
    be1 = create_backend(inst, 'userRootA', 'dc=example,dc=com')
    be2 = create_backend(inst, 'userRootB', 'dc=com,dc=example')
    # Okay, now we create the mapping trees for these backends, and we *invert* them in the parent config setting
    mts = MappingTrees(inst)
    mtb = mts.create(
        properties={
            'cn': 'dc=example,dc=com',
            'nsslapd-state': 'backend',
            'nsslapd-backend': 'userRootA',
        })
    mta = mts.create(
        properties={
            'cn': 'dc=com,dc=example',
            'nsslapd-state': 'backend',
            'nsslapd-backend': 'userRootB',
        })

    dc_ex = Domain(inst, dn='dc=example,dc=com')
    assert dc_ex.exists()

    dc_ab = Domain(inst, dn='dc=com,dc=example')
    assert dc_ab.exists()

    # Restart and check again
    inst.restart()
    assert dc_ex.exists()
    assert dc_ab.exists()
예제 #3
0
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()
예제 #4
0
def test_mapping_tree_inverted(topology):
    """Test the results of an inverted parent suffix definition in the configuration.

    For more details see:
    https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html

    :id: 024c4960-3aac-4d05-bc51-963dfdeb16ca

    :setup: Standalone instance (no backends)

    :steps:
        1. Add two backends without mapping trees.
        2. Add the mapping trees with inverted parent-suffix definitions.
        3. Attempt to search the definitions

    :expectedresults:
        1. Success
        2. Success
        3. The search suceed and can see validly arranged entries.
    """
    inst = topology.standalone
    # First create two Backends, without mapping trees.
    be1 = create_backend(inst, 'userRootA', 'dc=example,dc=com')
    be2 = create_backend(inst, 'userRootB', 'dc=straya,dc=example,dc=com')
    # Okay, now we create the mapping trees for these backends, and we *invert* them in the parent config setting
    mts = MappingTrees(inst)
    mtb = mts.create(
        properties={
            'cn': 'dc=straya,dc=example,dc=com',
            'nsslapd-state': 'backend',
            'nsslapd-backend': 'userRootB',
        })
    mta = mts.create(
        properties={
            'cn': 'dc=example,dc=com',
            'nsslapd-state': 'backend',
            'nsslapd-backend': 'userRootA',
            'nsslapd-parent-suffix': 'dc=straya,dc=example,dc=com'
        })

    dc_ex = Domain(inst, dn='dc=example,dc=com')
    assert dc_ex.exists()

    dc_st = Domain(inst, dn='dc=straya,dc=example,dc=com')
    assert dc_st.exists()

    # Restart and check again
    inst.restart()
    assert dc_ex.exists()
    assert dc_st.exists()
예제 #5
0
def test_mappingtree(topology):

    mts = MappingTrees(topology.standalone)
    mt = mts.create(properties={
        'cn': ["dc=newexample,dc=com",],
        'nsslapd-state' : 'backend',
        'nsslapd-backend' : 'someRoot',
        })

    rmt = mts.get('someRoot')
    rmt.delete()
예제 #6
0
def test_mapping_tree_many_shallow(topology):
    inst = topology.standalone
    dcs = [('dc=x%s,dc=example,dc=com' % x, 'userRoot%s' % x)
           for x in range(0, 50)]

    for (dc, bename) in dcs:
        create_backend(inst, bename, dc)

    mts = MappingTrees(inst)
    for (dc, bename) in dcs:
        mts.create(properties={
            'cn': dc,
            'nsslapd-state': 'backend',
            'nsslapd-backend': bename,
        })

    dc_asserts = [Domain(inst, dn=dc[0]) for dc in dcs]
    for dc_a in dc_asserts:
        assert dc_a.exists()
    inst.restart()
    for dc_a in dc_asserts:
        assert dc_a.exists()
예제 #7
0
def test_mapping_tree_many_deep_nesting(topology):
    inst = topology.standalone
    be_count = 0
    dcs = []
    for x in range(0, 10):
        dcs.append(('dc=x%s,dc=example,dc=com' % x, 'userRoot%s' % be_count))
        be_count += 1

    # Now add some children.
    for x in range(0, 10):
        dcs.append(
            ('dc=nest,dc=x%s,dc=example,dc=com' % x, 'userRoot%s' % be_count))
        be_count += 1

    #  Now add nested children
    for x in range(0, 10):
        for y in range(0, 5):
            dcs.append(('dc=y%s,dc=nest,dc=x%s,dc=example,dc=com' % (y, x),
                        'userRoot%s' % be_count))
            be_count += 1

    for (dc, bename) in dcs:
        create_backend(inst, bename, dc)

    mts = MappingTrees(inst)
    for (dc, bename) in dcs:
        mts.create(properties={
            'cn': dc,
            'nsslapd-state': 'backend',
            'nsslapd-backend': bename,
        })

    dc_asserts = [Domain(inst, dn=dc[0]) for dc in dcs]
    for dc_a in dc_asserts:
        assert dc_a.exists()
    inst.restart()
    for dc_a in dc_asserts:
        assert dc_a.exists()
예제 #8
0
def test_mapping_tree_nonexist_parent(topology):
    """Test a backend whos mapping tree definition has a non-existant parent-suffix

    For more details see:
    https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html

    :id: 7a9a09bd-7604-48f7-93cb-abff9e0d0131

    :setup: Standalone instance (no backends)

    :steps:
        1. Add one backend without mapping tree
        2. Configure the mapping tree with a non-existant parent suffix
        3. Attempt to search the backend

    :expectedresults:
        1. Success
        2. Success
        3. The search suceed and can see validly entries.
    """
    inst = topology.standalone
    be1 = create_backend(inst, 'userRootC', 'dc=test,dc=com')
    mts = MappingTrees(inst)
    mta = mts.create(
        properties={
            'cn': 'dc=test,dc=com',
            'nsslapd-state': 'backend',
            'nsslapd-backend': 'userRootC',
            'nsslapd-parent-suffix': 'dc=com'
        })
    # In this case the MT is never joined properly to the hierachy because the parent suffix
    # doesn't exist. The config is effectively ignored. That means that it can't be searched!
    dc_ex = Domain(inst, dn='dc=test,dc=com')
    assert dc_ex.exists()
    # Restart and check again.
    inst.restart()
    assert dc_ex.exists()
예제 #9
0
def create_backend(inst, rdn, suffix):
    # We only support dc= in this test.
    assert suffix.startswith('dc=')
    be1 = Backend(inst)
    be1.create(properties={
        'cn': rdn,
        'nsslapd-suffix': suffix,
    },
               create_mapping_tree=False)

    # Now we temporarily make the MT for this node so we can add the base entry.
    mts = MappingTrees(inst)
    mt = mts.create(properties={
        'cn': suffix,
        'nsslapd-state': 'backend',
        'nsslapd-backend': rdn,
    })

    # Create the domain entry
    create_base_domain(inst, suffix)
    # Now delete the mt
    mt.delete()

    return be1
예제 #10
0
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)
예제 #11
0
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"""

        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=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()
예제 #12
0
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