Exemple #1
0
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
Exemple #2
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"""
        # 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)
Exemple #3
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
Exemple #4
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=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)
Exemple #7
0
    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)