[MERGE] merge with latest stable
[odoo/odoo.git] / addons / users_ldap / users_ldap.py
index 7114bd4..14d985e 100644 (file)
 ##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
-# Copyright (c) 2004-2007 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
 #
-# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
+#    This program 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 Affero General Public License for more details.
 #
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
 #
 ##############################################################################
 
-from osv import fields,osv
-from service import security
+from osv import fields, osv
 import pooler
+import tools
+import logging
+from service import security
+import ldap
+from ldap.filter import filter_format
 
-try:
-       import ldap
-except ImportError:
-       import netsvc
-       logger = netsvc.Logger()
-       logger.notifyChannel("init", netsvc.LOG_WARNING, "could not import ldap!")
 
-class res_company(osv.osv):
-       _inherit = "res.company"
+class CompanyLDAP(osv.osv):
+    _name = 'res.company.ldap'
+    _order = 'sequence'
+    _rec_name = 'ldap_server'
+    _columns = {
+        'sequence': fields.integer('Sequence'),
+        'company': fields.many2one('res.company', 'Company', required=True,
+            ondelete='cascade'),
+        'ldap_server': fields.char('LDAP Server address', size=64, required=True),
+        'ldap_server_port': fields.integer('LDAP Server port', required=True),
+        'ldap_binddn': fields.char('LDAP binddn', size=64, required=True),
+        'ldap_password': fields.char('LDAP password', size=64, required=True),
+        'ldap_filter': fields.char('LDAP filter', size=64, required=True),
+        'ldap_base': fields.char('LDAP base', size=64, required=True),
+        'user': fields.many2one('res.users', 'Model User',
+            help="Model used for user creation"),
+        'create_user': fields.boolean('Create user',
+            help="Create the user if not in database"),
+    }
+    _defaults = {
+        'ldap_server': lambda *a: '127.0.0.1',
+        'ldap_server_port': lambda *a: 389,
+        'sequence': lambda *a: 10,
+        'create_user': lambda *a: True,
+    }
 
-       _columns = {
-               'ldap_server': fields.char('LDAP Server address', size=64),
-               'ldap_binddn': fields.char('LDAP binddn', size=64),
-               'ldap_password': fields.char('LDAP password', size=64),
-               'ldap_filter': fields.char('LDAP filter', size=64),
-               'ldap_base': fields.char('LDAP base', size=64),
-       }
+CompanyLDAP()
+
+
+class res_company(osv.osv):
+    _inherit = "res.company"
+    _columns = {
+        'ldaps': fields.one2many('res.company.ldap', 'company', 'LDAP Parameters'),
+    }
 res_company()
 
-def ldap_login(oldfnc):
-       def _ldap_login(db, login, passwd):
-               cr = pooler.get_db(db).cursor()
-               module_obj = pooler.get_pool(cr.dbname).get('ir.module.module')
-               module_ids = module_obj.search(cr, 1, [('name', '=', 'users_ldap')])
-               if module_ids:
-                       state = module_obj.read(cr, 1, module_ids, ['state'])[0]['state']
-                       if state in ('installed', 'to upgrade', 'to remove'):
-                               cr.execute("select id, name, ldap_server, ldap_binddn, ldap_password, ldap_filter, ldap_base from res_company where ldap_server != '' and ldap_binddn != ''")
-                               for res_company in cr.dictfetchall():
-                                       try:
-                                               l = ldap.open(res_company['ldap_server'])
-                                               if l.simple_bind_s(res_company['ldap_binddn'], res_company['ldap_password']):
-                                                       base = res_company['ldap_base']
-                                                       scope = ldap.SCOPE_SUBTREE
-                                                       filter = res_company['ldap_filter']%(login,)
-                                                       retrieve_attributes = None
-                                                       result_id = l.search(base, scope, filter, retrieve_attributes)
-                                                       timeout = 60
-                                                       result_type, result_data = l.result(result_id, timeout)
-                                                       if not result_data:
-                                                               continue
-                                                       if result_type == ldap.RES_SEARCH_RESULT and len(result_data) == 1:
-                                                               dn=result_data[0][0]
-                                                               name=result_data[0][1]['cn'][0]
-                                                               if l.bind_s(dn, passwd):
-                                                                       l.unbind()
-                                                                       cr.execute("select id from res_users where login=%s",(login.encode('utf-8'),))
-                                                                       res = cr.fetchone()
-                                                                       if res:
-                                                                               cr.close()
-                                                                               return res[0]
-                                                                       users_obj = pooler.get_pool(cr.dbname).get('res.users')
-                                                                       action_obj = pooler.get_pool(cr.dbname).get('ir.actions.actions')
-                                                                       action_id = action_obj.search(cr, 1, [('usage', '=', 'menu')])[0]
-                                                                       res = users_obj.create(cr, 1, {'name': name, 'login': login.encode('utf-8'), 'company_id': res_company['id'], 'action_id': action_id})
-                                                                       cr.commit()
-                                                                       cr.close()
-                                                                       return res
-                                                       l.unbind()
-                                       except Exception, e:
-                                               continue
-               cr.close()
-               return oldfnc(db, login, passwd)
-       return _ldap_login
+class users(osv.osv):
+    _inherit = "res.users"
+    def login(self, db, login, password):
+
+        if not password:
+            # empty passwords are disallowed for obvious security reasons
+            return False
+
+        ret = super(users,self).login(db, login, password)
+        if ret:
+            return ret
+        logger = logging.getLogger('orm.ldap')
+        pool = pooler.get_pool(db)
+        cr = pooler.get_db(db).cursor()
+        action_obj = pool.get('ir.actions.actions')
+        cr.execute("""
+            SELECT id, company, ldap_server, ldap_server_port, ldap_binddn, ldap_password,
+                   ldap_filter, ldap_base, "user", create_user
+            FROM res_company_ldap
+            WHERE ldap_server != '' and ldap_binddn != '' ORDER BY sequence""")
+        for res_company_ldap in cr.dictfetchall():
+            logger.debug(res_company_ldap)
+            try:
+                l = ldap.open(res_company_ldap['ldap_server'], res_company_ldap['ldap_server_port'])
+                if l.simple_bind_s(res_company_ldap['ldap_binddn'], res_company_ldap['ldap_password']):
+                    base = res_company_ldap['ldap_base']
+                    scope = ldap.SCOPE_SUBTREE
+                    filter = filter_format(res_company_ldap['ldap_filter'], (login,))
+                    retrieve_attributes = None
+                    result_id = l.search(base, scope, filter, retrieve_attributes)
+                    timeout = 60
+                    result_type, result_data = l.result(result_id, timeout)
+                    if not result_data:
+                        continue
+                    if result_type == ldap.RES_SEARCH_RESULT and len(result_data) == 1:
+                        dn = result_data[0][0]
+                        logger.debug(dn)
+                        name = result_data[0][1]['cn'][0]
+                        if l.bind_s(dn, password):
+                            l.unbind()
+                            cr.execute("SELECT id FROM res_users WHERE login=%s",(tools.ustr(login),))
+                            res = cr.fetchone()
+                            logger.debug(res)
+                            if res:
+                                cr.close()
+                                return res[0]
+                            if not res_company_ldap['create_user']:
+                                continue
+                            action_id = action_obj.search(cr, 1, [('usage', '=', 'menu')])[0]
+                            if res_company_ldap['user']:
+                                res = self.copy(cr, 1, res_company_ldap['user'],
+                                        default={'active': True})
+                                self.write(cr, 1, res, {
+                                    'name': name,
+                                    'login': login.encode('utf-8'),
+                                    'company_id': res_company_ldap['company'],
+                                    })
+                            else:
+                                res = self.create(cr, 1, {
+                                    'name': name,
+                                    'login': login.encode('utf-8'),
+                                    'company_id': res_company_ldap['company'],
+                                    'action_id': action_id,
+                                    'menu_id': action_id,
+                                    })
+                            cr.commit()
+                            cr.close()
+                            return res
+                    l.unbind()
+            except Exception:
+                logger.warning("Cannot auth", exc_info=True)
+                continue
+        cr.close()
+        return False
+
+    def check(self, db, uid, passwd):
+        try:
+            return super(users,self).check(db, uid, passwd)
+        except security.ExceptionNoTb: # AccessDenied
+            pass
 
-security.login = ldap_login(security.login)
+        if not passwd:
+            # empty passwords disallowed for obvious security reasons
+            raise security.ExceptionNoTb('AccessDenied')
 
-def ldap_check(oldfnc):
-       def _ldap_check(db, uid, passwd):
-               if security._uid_cache.has_key(uid) and (security._uid_cache[uid]==passwd):
-                       return True
-               cr = pooler.get_db(db).cursor()
-               module_obj = pooler.get_pool(cr.dbname).get('ir.module.module')
-               module_ids = module_obj.search(cr, 1, [('name', '=', 'users_ldap')])
-               if module_ids:
-                       state = module_obj.read(cr, 1, module_ids, ['state'])[0]['state']
-                       if state in ('installed', 'to upgrade', 'to remove'):
-                               users_obj = pooler.get_pool(cr.dbname).get('res.users')
-                               user = users_obj.browse(cr, 1, uid)
-                               if user and user.company_id.ldap_server and user.company_id.ldap_binddn:
-                                       company = user.company_id
-                                       try:
-                                               l = ldap.open(company.ldap_server)
-                                               if l.simple_bind_s(company.ldap_binddn, company.ldap_password):
-                                                       base = company['ldap_base']
-                                                       scope = ldap.SCOPE_SUBTREE
-                                                       filter = company['ldap_filter']%(user.login,)
-                                                       retrieve_attributes = None
-                                                       result_id = l.search(base, scope, filter, retrieve_attributes)
-                                                       timeout = 60
-                                                       result_type, result_data = l.result(result_id, timeout)
-                                                       if result_data and result_type == ldap.RES_SEARCH_RESULT and len(result_data) == 1:
-                                                               dn=result_data[0][0]
-                                                               name=result_data[0][1]['cn']
-                                                               if l.bind_s(dn, passwd):
-                                                                       l.unbind()
-                                                                       security._uid_cache[uid] = passwd
-                                                                       cr.close()
-                                                                       return True
-                                                       l.unbind()
-                                       except Exception, e:
-                                               pass
-               cr.close()
-               return oldfnc(db, uid, passwd)
-       return _ldap_check
+        cr = pooler.get_db(db).cursor()
+        user = self.browse(cr, 1, uid)
+        logger = logging.getLogger('orm.ldap')
+        if user and user.company_id.ldaps:
+            for res_company_ldap in user.company_id.ldaps:
+                try:
+                    l = ldap.open(res_company_ldap.ldap_server, res_company_ldap.ldap_server_port)
+                    if l.simple_bind_s(res_company_ldap.ldap_binddn,
+                            res_company_ldap.ldap_password):
+                        base = res_company_ldap.ldap_base
+                        scope = ldap.SCOPE_SUBTREE
+                        filter = filter_format(res_company_ldap.ldap_filter, (user.login,))
+                        retrieve_attributes = None
+                        result_id = l.search(base, scope, filter, retrieve_attributes)
+                        timeout = 60
+                        result_type, result_data = l.result(result_id, timeout)
+                        if result_data and result_type == ldap.RES_SEARCH_RESULT and len(result_data) == 1:
+                            dn = result_data[0][0]
+                            # some LDAP servers allow anonymous binding with blank passwords,
+                            # but these have been rejected above, so we're safe to use bind()
+                            if l.bind_s(dn, passwd):
+                                l.unbind()
+                                self._uid_cache.setdefault(db, {})[uid] = passwd
+                                cr.close()
+                                return True
+                        l.unbind()
+                except Exception:
+                    logger.warning('cannot check', exc_info=True)
+                    pass
+        cr.close()
+        raise security.ExceptionNoTb('AccessDenied')
+        
+users()
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
-security.check = ldap_check(security.check)