diff options
| author | Kinjal Mehta <kme@odoo.com> | 2015-09-02 14:53:08 +0530 |
|---|---|---|
| committer | Yannick Tivisse <yti@odoo.com> | 2016-07-29 15:15:47 +0200 |
| commit | 64ac8caa060f7067adc8c7e3bd381a6f5350e0d8 (patch) | |
| tree | a89bab6294f79dd5adfcd9e8e82c86fc3edaf232 /addons/auth_ldap/models | |
| parent | e6ab21be581729fbacc95639757d8e4916d3be00 (diff) | |
[MOVE] auth_ldap: Move files in related models and views directory
Diffstat (limited to 'addons/auth_ldap/models')
| -rw-r--r-- | addons/auth_ldap/models/__init__.py | 3 | ||||
| -rw-r--r-- | addons/auth_ldap/models/users_ldap.py | 267 |
2 files changed, 270 insertions, 0 deletions
diff --git a/addons/auth_ldap/models/__init__.py b/addons/auth_ldap/models/__init__.py new file mode 100644 index 00000000000..424954595f5 --- /dev/null +++ b/addons/auth_ldap/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import users_ldap diff --git a/addons/auth_ldap/models/users_ldap.py b/addons/auth_ldap/models/users_ldap.py new file mode 100644 index 00000000000..c9de7c5f15f --- /dev/null +++ b/addons/auth_ldap/models/users_ldap.py @@ -0,0 +1,267 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import ldap +import logging +from ldap.filter import filter_format + +import openerp.exceptions +from openerp import tools +from openerp.osv import fields, osv +from openerp import SUPERUSER_ID +from openerp.modules.registry import RegistryManager +_logger = logging.getLogger(__name__) + +class CompanyLDAP(osv.osv): + _name = 'res.company.ldap' + _order = 'sequence' + _rec_name = 'ldap_server' + + def get_ldap_dicts(self, cr, ids=None): + """ + Retrieve res_company_ldap resources from the database in dictionary + format. + + :param list ids: Valid ids of model res_company_ldap. If not \ + specified, process all resources (unlike other ORM methods). + :return: ldap configurations + :rtype: list of dictionaries + """ + + if ids: + id_clause = 'AND id IN (%s)' + args = [tuple(ids)] + else: + id_clause = '' + args = [] + cr.execute(""" + SELECT id, company, ldap_server, ldap_server_port, ldap_binddn, + ldap_password, ldap_filter, ldap_base, "user", create_user, + ldap_tls + FROM res_company_ldap + WHERE ldap_server != '' """ + id_clause + """ ORDER BY sequence + """, args) + return cr.dictfetchall() + + def connect(self, conf): + """ + Connect to an LDAP server specified by an ldap + configuration dictionary. + + :param dict conf: LDAP configuration + :return: an LDAP object + """ + + uri = 'ldap://%s:%d' % (conf['ldap_server'], + conf['ldap_server_port']) + + connection = ldap.initialize(uri) + if conf['ldap_tls']: + connection.start_tls_s() + return connection + + def authenticate(self, conf, login, password): + """ + Authenticate a user against the specified LDAP server. + + In order to prevent an unintended 'unauthenticated authentication', + which is an anonymous bind with a valid dn and a blank password, + check for empty passwords explicitely (:rfc:`4513#section-6.3.1`) + + :param dict conf: LDAP configuration + :param login: username + :param password: Password for the LDAP user + :return: LDAP entry of authenticated user or False + :rtype: dictionary of attributes + """ + + if not password: + return False + + entry = False + try: + filter = filter_format(conf['ldap_filter'], (login,)) + except TypeError: + _logger.warning('Could not format LDAP filter. Your filter should contain one \'%s\'.') + return False + try: + results = self.query(conf, filter) + + # Get rid of (None, attrs) for searchResultReference replies + results = [i for i in results if i[0]] + if results and len(results) == 1: + dn = results[0][0] + conn = self.connect(conf) + conn.simple_bind_s(dn, password.encode('utf-8')) + conn.unbind() + entry = results[0] + except ldap.INVALID_CREDENTIALS: + return False + except ldap.LDAPError, e: + _logger.error('An LDAP exception occurred: %s', e) + return entry + + def query(self, conf, filter, retrieve_attributes=None): + """ + Query an LDAP server with the filter argument and scope subtree. + + Allow for all authentication methods of the simple authentication + method: + + - authenticated bind (non-empty binddn + valid password) + - anonymous bind (empty binddn + empty password) + - unauthenticated authentication (non-empty binddn + empty password) + + .. seealso:: + :rfc:`4513#section-5.1` - LDAP: Simple Authentication Method. + + :param dict conf: LDAP configuration + :param filter: valid LDAP filter + :param list retrieve_attributes: LDAP attributes to be retrieved. \ + If not specified, return all attributes. + :return: ldap entries + :rtype: list of tuples (dn, attrs) + + """ + + results = [] + try: + conn = self.connect(conf) + ldap_password = conf['ldap_password'] or '' + ldap_binddn = conf['ldap_binddn'] or '' + conn.simple_bind_s(ldap_binddn.encode('utf-8'), ldap_password.encode('utf-8')) + results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE, + filter, retrieve_attributes, timeout=60) + conn.unbind() + except ldap.INVALID_CREDENTIALS: + _logger.error('LDAP bind failed.') + except ldap.LDAPError, e: + _logger.error('An LDAP exception occurred: %s', e) + return results + + def map_ldap_attributes(self, cr, uid, conf, login, ldap_entry): + """ + Compose values for a new resource of model res_users, + based upon the retrieved ldap entry and the LDAP settings. + + :param dict conf: LDAP configuration + :param login: the new user's login + :param tuple ldap_entry: single LDAP result (dn, attrs) + :return: parameters for a new resource of model res_users + :rtype: dict + """ + + values = { 'name': ldap_entry[1]['cn'][0], + 'login': login, + 'company_id': conf['company'] + } + return values + + def get_or_create_user(self, cr, uid, conf, login, ldap_entry, + context=None): + """ + Retrieve an active resource of model res_users with the specified + login. Create the user if it is not initially found. + + :param dict conf: LDAP configuration + :param login: the user's login + :param tuple ldap_entry: single LDAP result (dn, attrs) + :return: res_users id + :rtype: int + """ + + user_id = False + login = tools.ustr(login.lower().strip()) + cr.execute("SELECT id, active FROM res_users WHERE lower(login)=%s", (login,)) + res = cr.fetchone() + if res: + if res[1]: + user_id = res[0] + elif conf['create_user']: + _logger.debug("Creating new Odoo user \"%s\" from LDAP" % login) + user_obj = self.pool['res.users'] + values = self.map_ldap_attributes(cr, uid, conf, login, ldap_entry) + if conf['user']: + values['active'] = True + user_id = user_obj.copy(cr, SUPERUSER_ID, conf['user'], + default=values) + else: + user_id = user_obj.create(cr, SUPERUSER_ID, values) + return user_id + + _columns = { + 'sequence': fields.integer('Sequence'), + 'company': fields.many2one('res.company', 'Company', required=True, + ondelete='cascade'), + 'ldap_server': fields.char('LDAP Server address', required=True), + 'ldap_server_port': fields.integer('LDAP Server port', required=True), + 'ldap_binddn': fields.char('LDAP binddn', + help=("The user account on the LDAP server that is used to query " + "the directory. Leave empty to connect anonymously.")), + 'ldap_password': fields.char('LDAP password', + help=("The password of the user account on the LDAP server that is " + "used to query the directory.")), + 'ldap_filter': fields.char('LDAP filter', required=True), + 'ldap_base': fields.char('LDAP base', required=True), + 'user': fields.many2one('res.users', 'Template User', + help="User to copy when creating new users"), + 'create_user': fields.boolean('Create user', + help="Automatically create local user accounts for new users authenticating via LDAP"), + 'ldap_tls': fields.boolean('Use TLS', + help="Request secure TLS/SSL encryption when connecting to the LDAP server. " + "This option requires a server with STARTTLS enabled, " + "otherwise all authentication attempts will fail."), + } + _defaults = { + 'ldap_server': '127.0.0.1', + 'ldap_server_port': 389, + 'sequence': 10, + 'create_user': True, + } + + + +class res_company(osv.osv): + _inherit = "res.company" + _columns = { + 'ldaps': fields.one2many( + 'res.company.ldap', 'company', 'LDAP Parameters', copy=True, groups="base.group_system"), + } + + +class users(osv.osv): + _inherit = "res.users" + def _login(self, db, login, password): + user_id = super(users, self)._login(db, login, password) + if user_id: + return user_id + registry = RegistryManager.get(db) + with registry.cursor() as cr: + cr.execute("SELECT id FROM res_users WHERE lower(login)=%s", (login,)) + res = cr.fetchone() + if res: + return False + ldap_obj = registry.get('res.company.ldap') + for conf in ldap_obj.get_ldap_dicts(cr): + entry = ldap_obj.authenticate(conf, login, password) + if entry: + user_id = ldap_obj.get_or_create_user( + cr, SUPERUSER_ID, conf, login, entry) + if user_id: + break + return user_id + + def check_credentials(self, cr, uid, password): + try: + super(users, self).check_credentials(cr, uid, password) + except openerp.exceptions.AccessDenied: + + cr.execute('SELECT login FROM res_users WHERE id=%s AND active=TRUE', + (int(uid),)) + res = cr.fetchone() + if res: + ldap_obj = self.pool['res.company.ldap'] + for conf in ldap_obj.get_ldap_dicts(cr): + if ldap_obj.authenticate(conf, res[0], password): + return + raise + |
