if no ldap connection is defined fallback to local database. issue4232 review12591003
176 lines
6.1 KiB
Python
176 lines
6.1 KiB
Python
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
#this repository contains the full copyright notices and license terms.
|
|
import logging
|
|
import urlparse
|
|
|
|
import ldap
|
|
from trytond.transaction import Transaction
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.config import config, parse_uri
|
|
|
|
__all__ = ['User']
|
|
__metaclass__ = PoolMeta
|
|
|
|
logger = logging.getLogger(__name__)
|
|
section = 'ldap_authentication'
|
|
|
|
|
|
def parse_ldap_url(uri):
|
|
unquote = urlparse.unquote
|
|
uri = parse_uri(uri)
|
|
dn = unquote(uri.path)[1:]
|
|
attributes, scope, filter_, extensions = (
|
|
uri.query.split('?') + [''] * 4)[:4]
|
|
if not scope:
|
|
scope = 'base'
|
|
extensions = urlparse.parse_qs(extensions)
|
|
return (uri, dn, unquote(attributes), unquote(scope), unquote(filter_),
|
|
extensions)
|
|
|
|
|
|
def ldap_connection():
|
|
uri = config.get(section, 'uri')
|
|
if not uri:
|
|
return
|
|
uri, _, _, _, _, extensions = parse_ldap_url(uri)
|
|
if uri.scheme.startswith('ldaps'):
|
|
scheme, port = 'ldaps', 636
|
|
else:
|
|
scheme, port = 'ldap', 389
|
|
conn = ldap.initialize('%s://%s:%s/' % (
|
|
scheme, uri.hostname, uri.port or port))
|
|
if config.getboolean(section, 'active_directory', 'False'):
|
|
conn.set_option(ldap.OPT_REFERRALS, 0)
|
|
if 'tls' in uri.scheme:
|
|
conn.start_tls_s()
|
|
|
|
bindname = extensions.get('bindname')
|
|
if not bindname:
|
|
bindname = extensions.get('!bindname')
|
|
if bindname:
|
|
# XXX find better way to get the password
|
|
conn.simple_bind_s(bindname, config.get(section, 'bind_pass'))
|
|
return conn
|
|
|
|
|
|
class User:
|
|
__name__ = 'res.user'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(User, cls).__setup__()
|
|
cls._error_messages.update({
|
|
'set_passwd_ldap_user': (
|
|
'You can not set the password of ldap user "%s".'),
|
|
})
|
|
|
|
@staticmethod
|
|
def ldap_search_user(login, con, attrs=None):
|
|
'''
|
|
Return the result of a ldap search for the login using the ldap
|
|
connection con based on connection.
|
|
The attributes values defined in attrs will be return.
|
|
'''
|
|
_, dn, _, scope, filter_, _ = parse_ldap_url(
|
|
config.get(section, 'uri'))
|
|
scope = {
|
|
'base': ldap.SCOPE_BASE,
|
|
'onelevel': ldap.SCOPE_ONELEVEL,
|
|
'subtree': ldap.SCOPE_SUBTREE,
|
|
}.get(scope)
|
|
uid = config.get(section, 'uid', 'uid')
|
|
if filter_:
|
|
filter_ = '(&(%s=%s)%s)' % (uid, login, filter_)
|
|
else:
|
|
filter_ = '(%s=%s)' % (uid, login)
|
|
|
|
result = con.search_s(dn, scope, filter_, attrs)
|
|
if config.get(section, 'active_directory'):
|
|
result = [x for x in result if x[0]]
|
|
if result and len(result) > 1:
|
|
logger.info('ldap_search_user found more than 1 user')
|
|
return result
|
|
|
|
@classmethod
|
|
def _check_passwd_ldap_user(cls, logins):
|
|
find = False
|
|
try:
|
|
con = ldap_connection()
|
|
if not con:
|
|
return
|
|
for login in logins:
|
|
if cls.ldap_search_user(login, con, attrs=[]):
|
|
find = True
|
|
break
|
|
except ldap.LDAPError, e:
|
|
logger.error('LDAPError: %s' % str(e))
|
|
if find:
|
|
cls.raise_user_error('set_passwd_ldap_user', (login.rec_name,))
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
tocheck = []
|
|
for values in vlist:
|
|
if values.get('password') and 'login' in values:
|
|
tocheck.append(values['login'])
|
|
if tocheck:
|
|
with Transaction().set_context(_check_access=False):
|
|
cls._check_passwd_ldap_user(tocheck)
|
|
return super(User, cls).create(vlist)
|
|
|
|
@classmethod
|
|
def write(cls, *args):
|
|
actions = iter(args)
|
|
for users, values in zip(actions, actions):
|
|
if values.get('password'):
|
|
logins = [x.login for x in users]
|
|
cls._check_passwd_ldap_user(logins)
|
|
super(User, cls).write(*args)
|
|
|
|
@classmethod
|
|
def set_preferences(cls, values, old_password=False):
|
|
if 'password' in values:
|
|
try:
|
|
con = ldap_connection()
|
|
if con:
|
|
user = cls(Transaction().user)
|
|
uid = config.get(section, 'uid', 'uid')
|
|
users = cls.ldap_search_user(user.login, con, attrs=[uid])
|
|
if users and len(users) == 1:
|
|
[(dn, attrs)] = users
|
|
if con.simple_bind_s(dn, old_password):
|
|
con.passwd_s(dn, old_password, values['password'])
|
|
values = values.copy()
|
|
del values['password']
|
|
else:
|
|
cls.raise_user_error('wrong_password')
|
|
except ldap.LDAPError, e:
|
|
logger.error('LDAPError: %s' % str(e))
|
|
super(User, cls).set_preferences(values, old_password=old_password)
|
|
|
|
@classmethod
|
|
def get_login(cls, login, password):
|
|
pool = Pool()
|
|
LoginAttempt = pool.get('res.user.login.attempt')
|
|
try:
|
|
con = ldap_connection()
|
|
if con:
|
|
uid = config.get(section, 'uid', 'uid')
|
|
users = cls.ldap_search_user(login, con, attrs=[uid])
|
|
if users and len(users) == 1:
|
|
[(dn, attrs)] = users
|
|
if password and con.simple_bind_s(dn, password):
|
|
user_id, _ = cls._get_login(login)
|
|
if user_id:
|
|
LoginAttempt.remove(login)
|
|
return user_id
|
|
elif config.getboolean(section, 'create_user'):
|
|
user, = cls.create([{
|
|
'name': attrs.get(uid, [login])[0],
|
|
'login': login,
|
|
}])
|
|
return user.id
|
|
except ldap.LDAPError, e:
|
|
logger.error('LDAPError: %s' % str(e))
|
|
return super(User, cls).get_login(login, password)
|