~camptocamp/partner-contact-management/7.0-add_birthdate_nbi

« back to all changes in this revision

Viewing changes to partner_address_ldap/address.py

  • Committer: Joël Grand-Guillaume
  • Date: 2011-08-12 12:33:12 UTC
  • Revision ID: joel.grandguillaume@camptocamp.com-20110812123312-b4tbq3s80ztvn5d4
[ADD] First commit of the first generic modules to move in our new public branch
(lp:c2c-addons/6.1  rev 1)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
# Copyright (c) 2010-2011 Camptocamp SA (http://www.camptocamp.com)
 
5
# All Right Reserved
 
6
#
 
7
# Author : Nicolas Bessi (Camptocamp), Thanks to Laurent Lauden for his code adaptation
 
8
# Active directory Donor: M. Benadiba (Informatique Assistances.fr)
 
9
# Contribution : Joel Grand-Guillaume
 
10
#
 
11
# WARNING: This program as such is intended to be used by professional
 
12
# programmers who take the whole responsability of assessing all potential
 
13
# consequences resulting from its eventual inadequacies and bugs
 
14
# End users who are looking for a ready-to-use solution with commercial
 
15
# garantees and support are strongly adviced to contract a Free Software
 
16
# Service Company
 
17
#
 
18
# This program is Free Software; you can redistribute it and/or
 
19
# modify it under the terms of the GNU General Public License
 
20
# as published by the Free Software Foundation; either version 2
 
21
# of the License, or (at your option) any later version.
 
22
#
 
23
# This program is distributed in the hope that it will be useful,
 
24
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
25
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
26
# GNU General Public License for more details.
 
27
#
 
28
# You should have received a copy of the GNU General Public License
 
29
# along with this program; if not, write to the Free Software
 
30
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
31
#
 
32
##############################################################################
 
33
#TODO FInd why company parameter are cached
 
34
import re
 
35
import unicodedata
 
36
import netsvc
 
37
try:
 
38
    import ldap
 
39
    import ldap.modlist
 
40
except :
 
41
    print 'python ldap not installed please install it in order to use this module'
 
42
 
 
43
 
 
44
from osv import osv, fields
 
45
from tools.translate import _
 
46
 
 
47
logger = netsvc.Logger()
 
48
 
 
49
class LdapConnMApper(object):
 
50
    """LdapConnMApper: push specific fields from the Terp Partner_contacts to the
 
51
        LDAP schema inetOrgPerson. Ldap bind options are stored in company.r"""
 
52
    def __init__(self, cursor, uid, osv_obj, context=None):
 
53
        """Initialize connexion to ldap by using parameter set in the current user compagny"""
 
54
        logger.notifyChannel("MY TOPIC", netsvc.LOG_DEBUG,
 
55
                             _('Initalize LDAP CONN'))
 
56
        self.USER_DN = ''
 
57
        self.CONTACT_DN = ''
 
58
        self.LDAP_SERVER = ''
 
59
        self.PASS = ''
 
60
        self.OU = ''
 
61
        self.connexion = ''
 
62
        self.ACTIVDIR = False
 
63
 
 
64
        #Reading ldap pref
 
65
        user = osv_obj.pool.get('res.users').browse(cursor, uid, uid, context=context)
 
66
        company = osv_obj.pool.get('res.company').browse(cursor,
 
67
                                                         uid,
 
68
                                                         user.company_id.id,
 
69
                                                         context=context)
 
70
        self.USER_DN = company.base_dn
 
71
        self.CONTACT_DN = company.contact_dn
 
72
        self.LDAP_SERVER = company.ldap_server
 
73
        self.PASS = company.passwd
 
74
        self.PORT = company.ldap_port
 
75
        self.OU = company.ounit
 
76
        self.ACTIVDIR = company.is_activedir
 
77
 
 
78
        mand = (self.USER_DN, self.CONTACT_DN, self.LDAP_SERVER , self.PASS, self.OU)
 
79
        if company.ldap_active:
 
80
            for param in mand:
 
81
                if not param:
 
82
                    raise osv.except_osv(_('Warning !'),
 
83
                                         _('An LDAP parameter is missing for company %s') % (company.name,))
 
84
 
 
85
    def get_connexion(self):
 
86
        """create a new ldap connexion"""
 
87
        logger.notifyChannel("LDAP Address", netsvc.LOG_DEBUG,
 
88
                             _('connecting to server ldap %s') % (self.LDAP_SERVER,))
 
89
        if self.PORT :
 
90
            self.connexion = ldap.open(self.LDAP_SERVER, self.PORT)
 
91
        else :
 
92
            self.connexion = ldap.open(self.LDAP_SERVER)
 
93
        self.connexion.simple_bind_s(self.USER_DN, self.PASS)
 
94
        return self.connexion
 
95
 
 
96
 
 
97
class LDAPAddress(osv.osv):
 
98
    """Override the CRUD of the objet in order to dynamically bind to ldap"""
 
99
    _inherit = 'res.partner.address'
 
100
    ldapMapper = None
 
101
 
 
102
    def init(self, cr):
 
103
        logger = netsvc.Logger()
 
104
        try:
 
105
            logger.notifyChannel(_('LDAP address init'),
 
106
                                 netsvc.LOG_INFO,
 
107
                                 _('try to ALTER TABLE res_partner_address RENAME '
 
108
                                   'column name to lastname ;'))
 
109
            cr.execute('ALTER TABLE res_partner_address RENAME column name to lastname ;')
 
110
 
 
111
        except Exception, e:
 
112
            cr.rollback()
 
113
            logger.notifyChannel(_('LDAP address init'),
 
114
                                 netsvc.LOG_INFO,
 
115
                                 _('Warning ! impossible to rename column name'
 
116
                                   ' into lastname, this is probabely aleready'
 
117
                                   '  done or does not exist'))
 
118
 
 
119
    def _compute_name(self, firstname, lastname):
 
120
        firstname = firstname or u''
 
121
        lastname = lastname or u''
 
122
        firstname = (u' '+ firstname).rstrip()
 
123
        return u"%s%s" % (lastname, firstname)
 
124
 
 
125
    def _name_get_fnc(self, cursor, uid, ids, name, arg, context=None):
 
126
        """Get the name (lastname + firstname), otherwise ''"""
 
127
        if not ids:
 
128
            return []
 
129
        reads = self.read(cursor, uid, ids, ['lastname', 'firstname'])
 
130
        res = []
 
131
        for record in reads:
 
132
            ## We want to have " firstname" or ""
 
133
            name = self._compute_name(record['firstname'], record['lastname'])
 
134
            res.append((record['id'], name))
 
135
        return dict(res)
 
136
 
 
137
#    TODO get the new version of name search not vulnerable to sql injections
 
138
#    def name_search(self, cursor, user, name, args=None, operator='ilike', context=None, limit=100):
 
139
#        if not context: context = {}
 
140
#        prep_name = '.*%s.*' %(name)
 
141
#        cursor.execute(("select id from res_partner_address where"
 
142
#                        " (to_ascii(convert( lastname, 'UTF8', 'LATIN1'),'LATIN-1')  ~* '%s'"
 
143
#                        " or to_ascii(convert( firstname, 'UTF8', 'LATIN1'),'LATIN-1')  ~* '%s')"
 
144
#                        " limit %s") % (prep_name, prep_name, limit))
 
145
#        res = cursor.fetchall()
 
146
#        if res:
 
147
#            res = [x[0] for x in res]
 
148
#        else:
 
149
#            res = []
 
150
#        # search in partner name to know if we are searching partner...
 
151
#        partner_obj=self.pool.get('res.partner')
 
152
#        part_len = len(res)-limit
 
153
#        if part_len > 0:
 
154
#            partner_res = partner_obj.search(cursor, user, [('name', 'ilike', name)],
 
155
#                                             limit=part_len, context=context)
 
156
#            for p in partner_res:
 
157
#                addresses = partner_obj.browse(cursor, user, p).address
 
158
#                # Take each contact and add it to
 
159
#                for add in addresses:
 
160
#                    res.append(add.id)
 
161
#        return self.name_get(cursor, user, res, context)
 
162
 
 
163
 
 
164
    _columns = {
 
165
        'firstname': fields.char('First name', size=256),
 
166
        'lastname': fields.char('Last name', size=256),
 
167
        'name': fields.function(_name_get_fnc, method=True,
 
168
                                type="char", size=512,
 
169
                                store=True, string='Contact Name',
 
170
                                help='Name generated from the first name and last name',
 
171
                                nodrop=True),
 
172
        'private_phone':fields.char('Private phone', size=128),
 
173
    }
 
174
 
 
175
    def create(self, cursor, uid, vals, context={}):
 
176
        self.getconn(cursor, uid, {})
 
177
        ids = None
 
178
        self.validate_entries(vals, cursor, uid, ids)
 
179
        tmp_id = super(LDAPAddress, self).create(cursor, uid,
 
180
                                                 vals, context)
 
181
        if self.ldaplinkactive(cursor, uid, context):
 
182
            self.saveLdapContact(tmp_id, vals, cursor, uid, context)
 
183
        return tmp_id
 
184
 
 
185
    def write(self, cursor, uid, ids, vals, context=None):
 
186
        context = context or {}
 
187
        self.getconn(cursor, uid, {})
 
188
        if not isinstance(ids, list):
 
189
            ids = [ids]
 
190
        if ids:
 
191
            self.validate_entries(vals, cursor, uid, ids)
 
192
        if context.has_key('init_mode') and context['init_mode'] :
 
193
            success = True
 
194
        else :
 
195
            success = super(LDAPAddress, self).write(cursor, uid, ids,
 
196
                                                     vals, context)
 
197
        if self.ldaplinkactive(cursor, uid, context):
 
198
            for address_id in ids:
 
199
                self.updateLdapContact(address_id, vals, cursor, uid, context)
 
200
        return success
 
201
 
 
202
    def unlink(self, cursor, uid, ids, context=None):
 
203
        if not context: context = {}
 
204
        if ids:
 
205
            self.getconn(cursor, uid, {})
 
206
            if not isinstance(ids, list):
 
207
                ids = [ids]
 
208
            if self.ldaplinkactive(cursor, uid, context):
 
209
                for id in ids:
 
210
                    self.removeLdapContact(id, cursor, uid)
 
211
        return super(LDAPAddress, self).unlink(cursor, uid, ids)
 
212
 
 
213
    def validate_entries(self, vals, cursor, uid, ids):
 
214
        """Validate data of an address based on the inetOrgPerson schema"""
 
215
        for val in vals :
 
216
            try :
 
217
                if isinstance(vals[val], basestring):
 
218
                    vals[val] = unicode(vals[val].decode('utf8'))
 
219
            except UnicodeError:
 
220
                logger.notifyChannel('LDAP encode', netsvc.LOG_DEBUG,
 
221
                                     'cannot unicode '+ vals[val])
 
222
                pass
 
223
 
 
224
        if ids is not None:
 
225
            if isinstance(ids, (int, long)):
 
226
                ids = [ids]
 
227
            if len(ids) == 1:
 
228
                self.addNeededFields(ids[0],vals,cursor,uid)
 
229
        email = vals.get('email', False)
 
230
        phone = vals.get('phone', False)
 
231
        fax = vals.get('fax', False)
 
232
        mobile = vals.get('mobile', False)
 
233
        lastname = vals.get('lastname', False)
 
234
        private_phone = vals.get('private_phone', False)
 
235
        if email :
 
236
            if re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$",
 
237
                        email) is None:
 
238
                raise osv.except_osv(_('Warning !'),
 
239
                                     _('Please enter a valid e-mail'))
 
240
        phones = (('phone', phone), ('fax', fax), ('mobile', mobile),
 
241
                  ('private_phone', private_phone))
 
242
        for phone_tuple in phones:
 
243
            phone_number = phone_tuple[1]
 
244
            if phone_number :
 
245
                if not phone_number.startswith('+'):
 
246
                    raise osv.except_osv(_('Warning !'),
 
247
                                         _('Please enter a valid phone number in %s'
 
248
                                           ' international format (i.e. leading +)') % phone_tuple[0])
 
249
 
 
250
    def getVals(self, att_name, key, vals, dico, uid, ids, cursor, context=None):
 
251
        """map to values to dict"""
 
252
        if not context: context = {}
 
253
        ## We explicitely test False value
 
254
        if vals.get(key, False) != False:
 
255
            dico[att_name] = vals[key]
 
256
        else :
 
257
            if context.get('init_mode'):
 
258
                return False
 
259
            tmp = self.read(cursor, uid, ids, [key], context={})
 
260
            if tmp.get(key, False) :
 
261
                dico[att_name] = tmp[key]
 
262
 
 
263
 
 
264
    def _un_unicodize_buf(self, in_buf):
 
265
        if isinstance(in_buf, unicode) :
 
266
            try:
 
267
                return in_buf.encode()
 
268
            except Exception, e:
 
269
                return unicodedata.normalize("NFKD", in_buf).encode('ascii','ignore')
 
270
        return in_buf
 
271
 
 
272
    def unUnicodize(self, indict) :
 
273
        """remove unicode data of modlist as unicode is not supported
 
274
            by python-ldap librairy till version 2.7"""
 
275
        for key in indict :
 
276
            if not isinstance(indict[key], list):
 
277
                indict[key] = self._un_unicodize_buf(indict[key])
 
278
            else:
 
279
                nonutfArray = []
 
280
                for val in indict[key] :
 
281
                    nonutfArray.append(self._un_unicodize_buf(val))
 
282
                indict[key] = nonutfArray
 
283
 
 
284
    def addNeededFields(self, id, vals, cursor, uid):
 
285
        keys = vals.keys()
 
286
        previousvalue = self.browse(cursor, uid, [id])[0]
 
287
        if not vals.get('partner_id'):
 
288
            vals['partner_id'] = previousvalue.partner_id.id
 
289
        values_to_check = ('email', 'phone', 'fax', 'mobile', 'firstname',
 
290
                           'lastname', 'private_phone', 'street', 'street2')
 
291
        for val in values_to_check:
 
292
            if not vals.get(val):
 
293
                vals[val] = previousvalue[val]
 
294
 
 
295
    def mappLdapObject(self, id, vals, cursor, uid, context):
 
296
        """Mapp ResPArtner adress to moddlist"""
 
297
        self.addNeededFields(id, vals, cursor, uid)
 
298
        conn = self.getconn(cursor, uid, {})
 
299
        keys = vals.keys()
 
300
        partner_obj=self.pool.get('res.partner')
 
301
        part_name = partner_obj.browse(cursor, uid, vals['partner_id']).name
 
302
        vals['partner'] = part_name
 
303
        name = self._compute_name(vals.get('firstname'), vals.get('lastname'))
 
304
        if name :
 
305
            cn = name
 
306
        else:
 
307
            cn = part_name
 
308
        if not vals.get('lastname') :
 
309
            vals['lastname'] = part_name
 
310
        contact_obj = {'objectclass' : ['inetOrgPerson'],
 
311
                       'uid': ['terp_'+str(id)],
 
312
                       'ou':[conn.OU],
 
313
                       'cn':[cn],
 
314
                       'sn':[vals['lastname']]}
 
315
        if not vals.get('street'):
 
316
            vals['street'] = u''
 
317
        if not vals.get('street2'):
 
318
            vals['street2'] = u''
 
319
        street_key = 'street'
 
320
        if self.getconn(cursor, uid, {}).ACTIVDIR :
 
321
            # ENTERING THE M$ Realm and it is weird
 
322
            # We manage the address
 
323
            street_key = 'streetAddress'
 
324
            contact_obj[street_key] = vals['street'] + "\r\n" + vals['street2']
 
325
            #we modifiy the class
 
326
            contact_obj['objectclass'] = ['top','person','organizationalPerson','inetOrgPerson','user']
 
327
            #we handle the country
 
328
            if vals.get('country_id') :
 
329
                country =  self.browse(cursor, uid, id).country_id
 
330
                if country :
 
331
                    vals['country_id'] = country.name
 
332
                    vals['c'] = country.code
 
333
                else :
 
334
                    vals['country_id'] = False
 
335
                    vals['c'] = False
 
336
            if vals.get('country_id', False) :
 
337
                self.getVals('co', 'country_id', vals, contact_obj, uid, id, cursor, context)
 
338
                self.getVals('c', 'c', vals, contact_obj, uid, id, cursor, context)
 
339
            # we compute the display name
 
340
            vals['display'] = '%s %s'%(vals['partner'], contact_obj['cn'][0])
 
341
            # we get the title
 
342
            if self.browse(cursor, uid, id).function:
 
343
                contact_obj['description'] = self.browse(cursor, uid, id).function.name
 
344
            # we replace carriage return
 
345
            if vals.get('comment', False):
 
346
                vals['comment'] = vals['comment'].replace("\n","\r\n")
 
347
            # Active directory specific fields
 
348
            self.getVals('company', 'partner' ,vals, contact_obj, uid, id, cursor, context)
 
349
            self.getVals('info', 'comment' ,vals, contact_obj, uid, id, cursor, context)
 
350
            self.getVals('displayName', 'partner' ,vals, contact_obj, uid, id, cursor, context)
 
351
            ## Web site management
 
352
            if self.browse(cursor, uid, id).partner_id.website:
 
353
                vals['website'] = self.browse(cursor, uid, id).partner_id.website
 
354
                self.getVals('wWWHomePage', 'website', vals, contact_obj, uid, id, cursor, context)
 
355
                del(vals['website'])
 
356
            self.getVals('title', 'title', vals, contact_obj, uid, id, cursor, context)
 
357
        else :
 
358
            contact_obj[street_key] = vals['street'] + u"\n" + vals['street2']
 
359
            self.getVals('o','partner' ,vals, contact_obj, uid, id, cursor, context)
 
360
 
 
361
        #Common attributes
 
362
        self.getVals('givenName', 'firstname',vals, contact_obj, uid, id, cursor, context)
 
363
        self.getVals('mail', 'email',vals, contact_obj, uid, id, cursor, context)
 
364
        self.getVals('telephoneNumber', 'phone',vals, contact_obj, uid, id, cursor, context)
 
365
        self.getVals('l', 'city',vals, contact_obj, uid, id, cursor, context)
 
366
        self.getVals('facsimileTelephoneNumber', 'fax',vals, contact_obj, uid, id, cursor, context)
 
367
        self.getVals('mobile', 'mobile',vals, contact_obj, uid, id, cursor, context)
 
368
        self.getVals('homePhone', 'private_phone',vals, contact_obj, uid, id, cursor, context)
 
369
        self.getVals('postalCode', 'zip',vals, contact_obj, uid, id, cursor, context)
 
370
        self.unUnicodize(contact_obj)
 
371
        return contact_obj
 
372
 
 
373
    def saveLdapContact(self, id, vals, cursor, uid, context=None):
 
374
        """save openerp adress to ldap"""
 
375
        contact_obj = self.mappLdapObject(id,vals,cursor,uid,context)
 
376
        conn = self.connectToLdap(cursor, uid, context=context)
 
377
        try:
 
378
            if self.getconn(cursor, uid, context).ACTIVDIR:
 
379
                conn.connexion.add_s("CN=%s,OU=%s,%s"%(contact_obj['cn'][0], conn.OU, conn.CONTACT_DN),
 
380
                                     ldap.modlist.addModlist(contact_obj))
 
381
            else:
 
382
                conn.connexion.add_s("uid=terp_%s,OU=%s,%s"%(str(id), conn.OU, conn.CONTACT_DN),
 
383
                                        ldap.modlist.addModlist(contact_obj))
 
384
        except Exception, e:
 
385
            raise e
 
386
        conn.connexion.unbind_s()
 
387
 
 
388
    def updateLdapContact(self, id, vals, cursor, uid, context):
 
389
        """update an existing contact with the data of OpenERP"""
 
390
        conn = self.connectToLdap(cursor,uid,context={})
 
391
        try:
 
392
            old_contatc_obj = self.getLdapContact(conn,id)
 
393
        except ldap.NO_SUCH_OBJECT:
 
394
            self.saveLdapContact(id,vals,cursor,uid,context)
 
395
            return
 
396
        contact_obj = self.mappLdapObject(id,vals,cursor,uid,context)
 
397
        if conn.ACTIVDIR:
 
398
            modlist = []
 
399
            for key, val in contact_obj.items() :
 
400
                if key in ('cn', 'uid', 'objectclass'):
 
401
                    continue
 
402
                if isinstance(val, list):
 
403
                    val = val[0]
 
404
                modlist.append((ldap.MOD_REPLACE, key, val))
 
405
        else :
 
406
            modlist = ldap.modlist.modifyModlist(old_contatc_obj[1], contact_obj)
 
407
        try:
 
408
            conn.connexion.modify_s(old_contatc_obj[0], modlist)
 
409
            conn.connexion.unbind_s()
 
410
        except Exception, e:
 
411
            raise e
 
412
 
 
413
    def removeLdapContact(self, id, cursor, uid):
 
414
        """Remove a contact from ldap"""
 
415
        conn = self.connectToLdap(cursor,uid,context={})
 
416
        to_delete = None
 
417
        try:
 
418
            to_delete = self.getLdapContact(conn,id)
 
419
        except ldap.NO_SUCH_OBJECT:
 
420
            logger.notifyChannel("Warning", netsvc.LOG_INFO,
 
421
                                 _("'no object to delete in ldap' %s") %(id))
 
422
        except Exception, e :
 
423
            raise e
 
424
        try:
 
425
            if to_delete :
 
426
                conn.connexion.delete_s(to_delete[0])
 
427
                conn.connexion.unbind_s()
 
428
        except Exception, e:
 
429
            raise e
 
430
 
 
431
    def getLdapContact(self, conn, id):
 
432
        result = conn.connexion.search_ext_s("ou=%s,%s"%(conn.OU,conn.CONTACT_DN),
 
433
                                             ldap.SCOPE_SUBTREE,
 
434
                                             "(&(objectclass=*)(uid=terp_"+str(id)+"))")
 
435
        if not result:
 
436
            raise ldap.NO_SUCH_OBJECT
 
437
        return result[0]
 
438
 
 
439
    def ldaplinkactive(self, cursor, uid, context=None):
 
440
        """Check if ldap is activated for this company"""
 
441
        user = self.pool.get('res.users').browse(cursor, uid, uid, context=context)
 
442
        company = self.pool.get('res.company').browse(cursor, uid,user.company_id.id, context=context)
 
443
        return company.ldap_active
 
444
 
 
445
    def getconn(self, cursor, uid, context=None):
 
446
        """LdapConnMApper"""
 
447
        if not self.ldapMapper :
 
448
            self.ldapMapper = LdapConnMApper(cursor, uid, self)
 
449
        return self.ldapMapper
 
450
 
 
451
    def connectToLdap(self, cursor, uid, context=None):
 
452
        """Reinitialize ldap connection"""
 
453
        #getting ldap pref
 
454
        if not self.ldapMapper :
 
455
            self.getconn(cursor, uid, context)
 
456
        self.ldapMapper.get_connexion()
 
457
        return self.ldapMapper
 
458
 
 
459
LDAPAddress()