sobjects/ldapparser.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
--- a/sobjects/ldapparser.py	Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,255 +0,0 @@
-# copyright 2011-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb ldap feed source
-
-unlike ldapuser source, this source is copy based and will import ldap content
-(beside passwords for authentication) into the system source.
-"""
-from six.moves import map, filter
-
-from logilab.common.decorators import cached, cachedproperty
-from logilab.common.shellutils import generate_password
-
-from cubicweb import Binary, ConfigurationError
-from cubicweb.server.utils import crypt_password
-from cubicweb.server.sources import datafeed
-from cubicweb.dataimport import stores, importer
-
-
-class UserMetaGenerator(stores.MetaGenerator):
-    """Specific metadata generator, used to see newly created user into their initial state.
-    """
-    @cached
-    def base_etype_dicts(self, entity):
-        entity, rels = super(UserMetaGenerator, self).base_etype_dicts(entity)
-        if entity.cw_etype == 'CWUser':
-            wf_state = self._cnx.execute('Any S WHERE ET default_workflow WF, ET name %(etype)s, '
-                                         'WF initial_state S', {'etype': entity.cw_etype}).one()
-            rels['in_state'] = wf_state.eid
-        return entity, rels
-
-
-class DataFeedLDAPAdapter(datafeed.DataFeedParser):
-    __regid__ = 'ldapfeed'
-    # attributes that may appears in source user_attrs dict which are not
-    # attributes of the cw user
-    non_attribute_keys = set(('email', 'eid', 'member', 'modification_date'))
-
-    @cachedproperty
-    def searchfilterstr(self):
-        """ ldap search string, including user-filter """
-        return '(&%s)' % ''.join(self.source.base_filters)
-
-    @cachedproperty
-    def searchgroupfilterstr(self):
-        """ ldap search string, including user-filter """
-        return '(&%s)' % ''.join(self.source.group_base_filters)
-
-    @cachedproperty
-    def user_source_entities_by_extid(self):
-        source = self.source
-        if source.user_base_dn.strip():
-            attrs = list(map(str, source.user_attrs.keys()))
-            return dict((userdict['dn'].encode('ascii'), userdict)
-                        for userdict in source._search(self._cw,
-                                                       source.user_base_dn,
-                                                       source.user_base_scope,
-                                                       self.searchfilterstr,
-                                                       attrs))
-        return {}
-
-    @cachedproperty
-    def group_source_entities_by_extid(self):
-        source = self.source
-        if source.group_base_dn.strip():
-            attrs = list(map(str, ['modifyTimestamp'] + list(source.group_attrs.keys())))
-            return dict((groupdict['dn'].encode('ascii'), groupdict)
-                        for groupdict in source._search(self._cw,
-                                                        source.group_base_dn,
-                                                        source.group_base_scope,
-                                                        self.searchgroupfilterstr,
-                                                        attrs))
-        return {}
-
-    def process(self, url, raise_on_error=False):
-        """IDataFeedParser main entry point"""
-        self.debug('processing ldapfeed source %s %s', self.source, self.searchfilterstr)
-        self._group_members = {}
-        eeimporter = self.build_importer(raise_on_error)
-        for name in self.source.user_default_groups:
-            geid = self._get_group(name)
-            eeimporter.extid2eid[geid] = geid
-        entities = self.extentities_generator()
-        set_cwuri = importer.use_extid_as_cwuri(eeimporter.extid2eid)
-        eeimporter.import_entities(set_cwuri(entities))
-        self.stats['created'] = eeimporter.created
-        self.stats['updated'] = eeimporter.updated
-        # handle in_group relation
-        for group, members in self._group_members.items():
-            self._cw.execute('DELETE U in_group G WHERE G name %(g)s', {'g': group})
-            if members:
-                members = ["'%s'" % e for e in members]
-                rql = 'SET U in_group G WHERE G name %%(g)s, U login IN (%s)' % ','.join(members)
-                self._cw.execute(rql, {'g': group})
-        # ensure updated users are activated
-        for eid in eeimporter.updated:
-            entity = self._cw.entity_from_eid(eid)
-            if entity.cw_etype == 'CWUser':
-                self.ensure_activated(entity)
-        # manually set primary email if necessary, it's not handled automatically since hooks are
-        # deactivated
-        self._cw.execute('SET X primary_email E WHERE NOT X primary_email E, X use_email E, '
-                         'X cw_source S, S eid %(s)s, X in_state ST, TS name "activated"',
-                         {'s': self.source.eid})
-
-    def build_importer(self, raise_on_error):
-        """Instantiate and configure an importer"""
-        etypes = ('CWUser', 'EmailAddress', 'CWGroup')
-        extid2eid = dict((self.source.decode_extid(x), y) for x, y in
-                self._cw.system_sql('select extid, eid from entities where asource = %(s)s', {'s': self.source.uri}))
-        existing_relations = {}
-        for rtype in ('in_group', 'use_email', 'owned_by'):
-            rql = 'Any S,O WHERE S {} O, S cw_source SO, SO eid %(s)s'.format(rtype)
-            rset = self._cw.execute(rql, {'s': self.source.eid})
-            existing_relations[rtype] = set(tuple(x) for x in rset)
-        return importer.ExtEntitiesImporter(self._cw.vreg.schema, self.build_store(),
-                                            extid2eid=extid2eid,
-                                            existing_relations=existing_relations,
-                                            etypes_order_hint=etypes,
-                                            import_log=self.import_log,
-                                            raise_on_error=raise_on_error)
-
-    def build_store(self):
-        """Instantiate and configure a store"""
-        metagenerator = UserMetaGenerator(self._cw, source=self.source)
-        return stores.NoHookRQLObjectStore(self._cw, metagenerator)
-
-    def extentities_generator(self):
-        self.debug('processing ldapfeed source %s %s', self.source, self.searchgroupfilterstr)
-        # generate users and email addresses
-        for userdict in self.user_source_entities_by_extid.values():
-            attrs = self.ldap2cwattrs(userdict, 'CWUser')
-            pwd = attrs.get('upassword')
-            if not pwd:
-                # generate a dumb password if not fetched from ldap (see
-                # userPassword)
-                pwd = crypt_password(generate_password())
-                attrs['upassword'] = set([Binary(pwd)])
-            extuser = importer.ExtEntity('CWUser', userdict['dn'].encode('ascii'), attrs)
-            extuser.values['owned_by'] = set([extuser.extid])
-            for extemail in self._process_email(extuser, userdict):
-                yield extemail
-            groups = list(filter(None, [self._get_group(name)
-                                        for name in self.source.user_default_groups]))
-            if groups:
-                extuser.values['in_group'] = groups
-            yield extuser
-        # generate groups
-        for groupdict in self.group_source_entities_by_extid.values():
-            attrs = self.ldap2cwattrs(groupdict, 'CWGroup')
-            extgroup = importer.ExtEntity('CWGroup', groupdict['dn'].encode('ascii'), attrs)
-            yield extgroup
-            # record group membership for later insertion
-            members = groupdict.get(self.source.group_rev_attrs['member'], ())
-            self._group_members[attrs['name']] = members
-
-    def _process_email(self, extuser, userdict):
-        try:
-            emailaddrs = userdict.pop(self.source.user_rev_attrs['email'])
-        except KeyError:
-            return  # no email for that user, nothing to do
-        if not isinstance(emailaddrs, list):
-            emailaddrs = [emailaddrs]
-        for emailaddr in emailaddrs:
-            # search for existing email first, may be coming from another source
-            rset = self._cw.execute('EmailAddress X WHERE X address %(addr)s',
-                                    {'addr': emailaddr})
-            emailextid = (userdict['dn'] + '@@' + emailaddr).encode('ascii')
-            if not rset:
-                # not found, create it. first forge an external id
-                extuser.values.setdefault('use_email', []).append(emailextid)
-                yield importer.ExtEntity('EmailAddress', emailextid, dict(address=[emailaddr]))
-            elif self.sourceuris:
-                # pop from sourceuris anyway, else email may be removed by the
-                # source once import is finished
-                self.sourceuris.pop(emailextid, None)
-            # XXX else check use_email relation?
-
-    def handle_deletion(self, config, cnx, myuris):
-        if config['delete-entities']:
-            super(DataFeedLDAPAdapter, self).handle_deletion(config, cnx, myuris)
-            return
-        if myuris:
-            for extid, (eid, etype) in myuris.items():
-                if etype != 'CWUser' or not self.is_deleted(extid, etype, eid):
-                    continue
-                self.info('deactivate user %s', eid)
-                wf = cnx.entity_from_eid(eid).cw_adapt_to('IWorkflowable')
-                wf.fire_transition_if_possible('deactivate')
-        cnx.commit()
-
-    def ensure_activated(self, entity):
-        if entity.cw_etype == 'CWUser':
-            wf = entity.cw_adapt_to('IWorkflowable')
-            if wf.state == 'deactivated':
-                wf.fire_transition('activate')
-                self.info('user %s reactivated', entity.login)
-
-    def ldap2cwattrs(self, sdict, etype):
-        """Transform dictionary of LDAP attributes to CW.
-
-        etype must be CWUser or CWGroup
-        """
-        assert etype in ('CWUser', 'CWGroup'), etype
-        tdict = {}
-        if etype == 'CWUser':
-            items = self.source.user_attrs.items()
-        elif etype == 'CWGroup':
-            items = self.source.group_attrs.items()
-        for sattr, tattr in items:
-            if tattr not in self.non_attribute_keys:
-                try:
-                    value = sdict[sattr]
-                except KeyError:
-                    raise ConfigurationError(
-                        'source attribute %s has not been found in the source, '
-                        'please check the %s-attrs-map field and the permissions of '
-                        'the LDAP binding user' % (sattr, etype[2:].lower()))
-                if not isinstance(value, list):
-                    value = [value]
-                tdict[tattr] = value
-        return tdict
-
-    def is_deleted(self, extidplus, etype, eid):
-        try:
-            extid = extidplus.rsplit(b'@@', 1)[0]
-        except ValueError:
-            # for some reason extids here tend to come in both forms, e.g:
-            # dn, dn@@Babar
-            extid = extidplus
-        return extid not in self.user_source_entities_by_extid
-
-    @cached
-    def _get_group(self, name):
-        try:
-            return self._cw.execute('Any X WHERE X is CWGroup, X name %(name)s',
-                                    {'name': name})[0][0]
-        except IndexError:
-            self.error('group %r referenced by source configuration %r does not exist',
-                       name, self.source.uri)
-            return None