diff --git a/pdsl_ldap_membership/ldap_membership.py b/pdsl_ldap_membership/ldap_membership.py index 492564823cff0f1f935dc4a59df0d297c4cc2003..86b9503e1e1184f5f176e183f67c8becd0735eff 100644 --- a/pdsl_ldap_membership/ldap_membership.py +++ b/pdsl_ldap_membership/ldap_membership.py @@ -6,10 +6,9 @@ # ############################################################################## - import base64 import hashlib -from ldap import modlist +from ldap import modlist import ldap import logging import os @@ -22,21 +21,19 @@ from openerp.exceptions import Warning, ValidationError from openerp.osv import fields from openerp.osv import osv - _logger = logging.getLogger(__name__) - ACTIVE_STATES = ('paid', 'invoiced', 'free') class Invoice(osv.Model): '''Invoice''' _inherit = 'account.invoice' - + # ---------------------------------------- # Invoice state management vs membership # ---------------------------------------- - + def invoice_validate(self, cr, uid, ids, context=None): """ Override to update membership line. @@ -51,8 +48,8 @@ class Invoice(osv.Model): [l.id for l in invoice.invoice_line])]) if mlines: member_line_obj.write(cr, uid, mlines, {'date_cancel': None}) - return res - + return res + def action_cancel_draft(self, cr, uid, ids, context=None): """ Override to update membership lines. @@ -80,7 +77,7 @@ class Invoice(osv.Model): # TODO Check if the invoice contains any membership lines ? return True return False - + @api.multi def write(self, vals): res = super(Invoice, self).write(vals) @@ -89,6 +86,7 @@ class Invoice(osv.Model): self.partner_id.pdsl_update_ldap_membership() return res + class ProductTemplate(osv.osv): """Add column to product""" _inherit = "product.template" @@ -99,7 +97,7 @@ class ProductTemplate(osv.osv): _defaults = { 'ldap_membership_group': False, } - + @api.multi def pdsl_update_ldap_group(self): """ @@ -110,7 +108,7 @@ class ProductTemplate(osv.osv): return _logger.info('updating LDAP product [%s]' % self.ldap_membership_group) - + # Connect to LDAP server ldap_obj = self.env['res.company.ldap'] for conf in ldap_obj.get_ldap_dicts(): @@ -122,7 +120,7 @@ class ProductTemplate(osv.osv): _logger.warn('connection to LDAP server failed', exc_info=1) if not l: raise ValueError('LDAP execution failed') - + def _new_gid(): """Query the ldap directory for a new gidNumber.""" # Get list of uid. Pick Max +1 @@ -132,9 +130,9 @@ class ProductTemplate(osv.osv): filterstr='(&(objectClass=posixGroup)(gidNumber=*))', attrlist=['gidNumber']) if len(r) > 0: - return max(int(u[1]['gidNumber'][0]) for u in r) + 1 + return max(int(u[1]['gidNumber'][0]) for u in r) + 1 raise ValueError("fail to find new gidNumber") - + changed = False try: for group_name in self.ldap_membership_group.split(','): @@ -145,7 +143,7 @@ class ProductTemplate(osv.osv): attrs = { 'cn': group_name_b, } - + # Search group in LDAP. search_filter = ldap.filter.filter_format('(&(objectClass=posixGroup)(cn=%s))', (group_name_b,)) r = l.search_s( @@ -158,19 +156,18 @@ class ProductTemplate(osv.osv): attrs['description'] = self.name.encode('utf-8') attrs['gidNumber'] = str(_new_gid()) attrs['objectclass'] = ['posixGroup'] - + # User doesn't exists. Add it dn = 'cn=%s,%s,%s' % (attrs['cn'], 'ou=Groups', conf['ldap_base']) ldif = modlist.addModlist(attrs) l.add_s(dn, ldif) - self.message_post(body=_("LDAP group %s created") % group_name) - changed = True + self.message_post(body=_("LDAP group %s created") % group_name) + changed = True finally: l.unbind_s() - + return changed - - + @api.multi def write(self, vals): """Override to create the group in LDAP.""" @@ -182,11 +179,11 @@ class ProductTemplate(osv.osv): # TODO Should update all membership if ldap_membership_group change. pass return res - + class ResPartner(models.Model): _inherit = "res.partner" - + # Declare new fields. _columns = { 'ldap_membership_login': fields.char('Membership Login', size=64, required=False, @@ -207,7 +204,7 @@ class ResPartner(models.Model): if not ldap_obj.get_ldap_dicts(): _logger.warn('skip LDAP membership update - LDAP server not configured') return - + # Connect to LDAP server for conf in ldap_obj.get_ldap_dicts(): try: @@ -243,7 +240,7 @@ class ResPartner(models.Model): username = re.sub('[ \-]', '.', username.lower()) # Remove invalid char return re.sub('[^a-z0-9.]', '', username) - + def _new_username(): """Generate a unused ldap username.""" candidates = [] @@ -261,15 +258,15 @@ class ResPartner(models.Model): attrsonly=1) if len(r) == 0: return c - raise ValueError('cannot generate a unique username') - + raise ValueError('cannot generate a unique username') + # Find a free user name. login = None try: login = _new_username() finally: l.unbind_s() - + # Update the user with this new user name. if login: self.write(vals={'ldap_membership_login': login}) @@ -286,13 +283,13 @@ class ResPartner(models.Model): if not active and not self.ldap_membership_login: # Update not required. return - + # Generate a new username. if not self.ldap_membership_login: self._pdsl_set_ldap_user() _logger.info('updating user [%s] membership' % self.ldap_membership_login) - + # Connect to LDAP server l, ldap_conf = self._connect() @@ -321,7 +318,7 @@ class ResPartner(models.Model): 'shadowExpire': str(int(expire_date // 86400)), # / 24 / 60 / 60 'description': groups_b, } - + # Search user in LDAP. search_filter = ldap.filter.filter_format(ldap_conf['ldap_filter'], (ldap_membership_login_b,)) r = l.search_s( @@ -333,7 +330,7 @@ class ResPartner(models.Model): # Raise exception user should exists. # Most likely user was deleted manually in LDAP. raise Warning(_("Failed to update LDAP membership.\nLDAP user %s doesn't exists!") % self.ldap_membership_login) - + # Update the record. dn = r[0][0] old = r[0][1] @@ -360,9 +357,9 @@ class ResPartner(models.Model): new['memberUid'].append(ldap_membership_login_b) ldif = modlist.modifyModlist(old, new) l.modify_s(dn, ldif) - + # TODO Remove user from other groups ? - + # Log this as an event. self.message_post( body=_("LDAP membership for user [%s] updated.
Member of: %s
Expired: %s") % (self.ldap_membership_login, ', '.join(groups), time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(expire_date)))) @@ -378,18 +375,18 @@ class ResPartner(models.Model): @api.multi def pdsl_update_ldap_user(self, force_reset_password=False): """Update or create the LDAP user.""" - + if not self.ldap_membership_login: return # Basic verification to make sure we have the right information. if not self.name: - raise Warning(_("Failed to update the LDAP membership.\nName field is required.")) + raise Warning(_("Failed to update the LDAP membership.\nName field is required.")) if not self.email: raise Warning(_("Failed to update the LDAP membership.\nEmail field is required.")) - + _logger.info('updating LDAP user %s' % self.ldap_membership_login) - + # Connect to LDAP server l, ldap_conf = self._connect() @@ -402,14 +399,14 @@ class ResPartner(models.Model): filterstr='(&(objectClass=posixAccount)(uidNumber=*))', attrlist=['uidNumber']) if len(r) > 0: - return max(int(u[1]['uidNumber'][0]) for u in r) + 1 + return max(int(u[1]['uidNumber'][0]) for u in r) + 1 raise Warning(_("fail to find new uidNumber")) - + def _new_password(length=8): """Generate a new password.""" table = string.lowercase + string.digits return ''.join([table[ord(c) % len(table)] for c in os.urandom(length)]) - + def _ssha(password): """Convert the password a SSHA (base64 SHA hash + salt)""" salt = os.urandom(4) @@ -417,7 +414,7 @@ class ResPartner(models.Model): sha.update(salt) digest_salt_b64 = '{}{}'.format(sha.digest(), salt).encode('base64').strip() return '{{SSHA}}{}'.format(digest_salt_b64) - + try: # Build up expected user (should be bytes) ldap_membership_login_b = self.ldap_membership_login.encode('utf-8') @@ -429,7 +426,7 @@ class ResPartner(models.Model): 'sn': name_b.partition(' ')[2] or name_b.partition(' ')[0], # Lastname (required) 'mail': email_b, } - + # Search user in LDAP. search_filter = ldap.filter.filter_format(ldap_conf['ldap_filter'], (ldap_membership_login_b,)) r = l.search_s( @@ -448,22 +445,22 @@ class ResPartner(models.Model): attrs['homeDirectory'] = '/home/%s' % (ldap_membership_login_b,) attrs['objectclass'] = ['inetOrgPerson', 'posixAccount', 'organizationalPerson', 'person', 'shadowAccount'] attrs['userPassword'] = _ssha(password) - + # User doesn't exists. Add it dn = 'uid=%s,%s,%s' % (attrs['uid'], 'ou=People', ldap_conf['ldap_base']) ldif = modlist.addModlist(attrs) l.add_s(dn, ldif) - + # Send username and password by mail. ctx = dict(self._context, password=password) template_id = self.env['ir.model.data'].xmlid_to_object('pdsl_ldap_membership.new_password_email_template') template_id.with_context(ctx).send_mail(self.id) - + self.message_post(body=_("LDAP user %s / %s created") % (self.ldap_membership_login, password)) - + # Also update the membership is a new user was created. self.pdsl_update_ldap_membership() - + else: # Generate a new password if required. if force_reset_password: @@ -475,15 +472,15 @@ class ResPartner(models.Model): old = r[0][1] ldif = modlist.modifyModlist(old, attrs) l.modify_s(dn, ldif) - + # Send username and password by mail. if force_reset_password: ctx = dict(self._context, password=password) template_id = self.env['ir.model.data'].xmlid_to_object('pdsl_ldap_membership.new_password_email_template') template_id.with_context(ctx).send_mail(self.id) - + self.message_post(body=_("LDAP user %s updated") % self.ldap_membership_login) - + finally: # Close connection l.unbind_s()