Files
tryton/modules/ldap_authentication/res.py
T

170 lines
6.1 KiB
Python
Raw Normal View History

2014-12-02 12:55:22 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2014-07-09 00:23:24 +02:00
import logging
import ssl
2018-06-26 19:11:48 +02:00
import urllib.parse
2014-07-09 00:23:24 +02:00
2016-06-03 16:40:43 +02:00
import ldap3
2017-01-14 20:54:20 +01:00
from ldap3.core.exceptions import LDAPException
2014-09-20 13:58:31 +02:00
from trytond.config import config, parse_uri
2016-05-31 12:55:54 +02:00
from trytond.exceptions import LoginException
2021-12-11 17:58:42 +01:00
from trytond.i18n import gettext
from trytond.model.exceptions import AccessError
from trytond.pool import PoolMeta
from trytond.transaction import Transaction
2012-09-24 22:17:56 +02:00
2014-07-09 00:23:24 +02:00
logger = logging.getLogger(__name__)
2014-09-20 13:58:31 +02:00
section = 'ldap_authentication'
2015-01-24 22:37:35 +01:00
# Old version of urlparse doesn't parse query for ldap
# see http://bugs.python.org/issue9374
2018-06-26 19:11:48 +02:00
if 'ldap' not in urllib.parse.uses_query:
urllib.parse.uses_query.append('ldap')
2015-01-24 22:37:35 +01:00
2014-09-20 13:58:31 +02:00
def parse_ldap_url(uri):
2018-06-26 19:11:48 +02:00
unquote = urllib.parse.unquote
2014-09-20 13:58:31 +02:00
uri = parse_uri(uri)
dn = unquote(uri.path)[1:]
attributes, scope, filter_, extensions = (
uri.query.split('?') + [''] * 4)[:4]
if not scope:
scope = 'base'
2018-06-26 19:11:48 +02:00
extensions = urllib.parse.parse_qs(extensions)
2014-09-20 13:58:31 +02:00
return (uri, dn, unquote(attributes), unquote(scope), unquote(filter_),
extensions)
2016-06-03 16:40:43 +02:00
def ldap_server():
2014-09-20 13:58:31 +02:00
uri = config.get(section, 'uri')
if not uri:
return
uri, _, _, _, _, extensions = parse_ldap_url(uri)
if uri.scheme.startswith('ldaps'):
scheme, port = 'ldaps', 636
tls = ldap3.Tls(validate=ssl.CERT_REQUIRED)
2014-09-20 13:58:31 +02:00
else:
scheme, port = 'ldap', 389
tls = None
2022-10-10 21:18:17 +02:00
if 'tls' in uri.scheme:
tls = ldap3.Tls(validate=ssl.CERT_REQUIRED)
2016-06-03 16:40:43 +02:00
return ldap3.Server('%s://%s:%s' % (
scheme, uri.hostname, uri.port or port), tls=tls)
2015-07-22 13:41:21 +07:00
2018-06-26 19:11:48 +02:00
class User(metaclass=PoolMeta):
2012-09-24 22:17:56 +02:00
__name__ = 'res.user'
2009-09-11 14:10:16 +02:00
2012-09-24 22:17:56 +02:00
@staticmethod
2016-06-03 16:40:43 +02:00
def ldap_search_user(login, server, attrs=None):
2009-09-11 14:10:16 +02:00
'''
2012-09-24 22:17:56 +02:00
Return the result of a ldap search for the login using the ldap
2016-06-03 16:40:43 +02:00
server.
2012-09-24 22:17:56 +02:00
The attributes values defined in attrs will be return.
2009-09-11 14:10:16 +02:00
'''
2016-06-03 16:40:43 +02:00
_, dn, _, scope, filter_, extensions = parse_ldap_url(
2014-09-20 13:58:31 +02:00
config.get(section, 'uri'))
2009-09-11 14:10:16 +02:00
scope = {
2016-06-03 16:40:43 +02:00
'base': ldap3.BASE,
'onelevel': ldap3.LEVEL,
'subtree': ldap3.SUBTREE,
2016-06-07 09:57:28 +02:00
}[scope]
2015-03-22 03:18:59 +01:00
uid = config.get(section, 'uid', default='uid')
2014-09-20 13:58:31 +02:00
if filter_:
2016-06-03 16:40:43 +02:00
filter_ = '(&(%s=%s)%s)' % (uid, login, filter_)
2009-09-11 14:10:16 +02:00
else:
2016-06-03 16:40:43 +02:00
filter_ = '(%s=%s)' % (uid, login)
2009-09-11 14:10:16 +02:00
2016-06-03 16:40:43 +02:00
bindpass = None
bindname, = extensions.get('bindname', [None])
if not bindname:
bindname, = extensions.get('!bindname', [None])
if bindname:
# XXX find better way to get the password
bindpass = config.get(section, 'bind_pass')
2022-10-10 21:18:17 +02:00
bind_method = ldap3.AUTO_BIND_DEFAULT
if server.ssl is False and server.tls is not None:
bind_method = ldap3.AUTO_BIND_TLS_BEFORE_BIND
with ldap3.Connection(
server, bindname, bindpass, auto_bind=bind_method) as con:
2016-06-03 16:40:43 +02:00
con.search(dn, filter_, search_scope=scope, attributes=attrs)
result = con.entries
if result and len(result) > 1:
logger.info('ldap_search_user found more than 1 user')
2017-01-14 20:54:20 +01:00
return [(e.entry_dn, e.entry_attributes_as_dict)
2016-06-03 16:40:43 +02:00
for e in result]
2009-09-11 14:10:16 +02:00
2012-09-24 22:17:56 +02:00
@classmethod
def _check_passwd_ldap_user(cls, logins):
2009-09-11 14:10:16 +02:00
find = False
try:
2016-06-03 16:40:43 +02:00
server = ldap_server()
if not server:
2014-10-06 22:30:55 +02:00
return
2009-09-11 14:10:16 +02:00
for login in logins:
2016-06-03 16:40:43 +02:00
if cls.ldap_search_user(login, server, attrs=[]):
2009-09-11 14:10:16 +02:00
find = True
break
2017-01-14 20:54:20 +01:00
except LDAPException:
2015-03-02 23:40:37 +01:00
logger.error('LDAPError when checking password', exc_info=True)
2009-09-11 14:10:16 +02:00
if find:
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('ldap_authentication.msg_ldap_user_change_password',
user=login))
2009-09-11 14:10:16 +02:00
2012-09-24 22:17:56 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
tocheck = []
for values in vlist:
if values.get('password') and 'login' in values:
tocheck.append(values['login'])
if tocheck:
2014-09-17 21:45:29 +02:00
with Transaction().set_context(_check_access=False):
cls._check_passwd_ldap_user(tocheck)
2013-01-06 22:18:13 +01:00
return super(User, cls).create(vlist)
2009-09-11 14:10:16 +02:00
2012-09-24 22:17:56 +02:00
@classmethod
2013-12-19 14:45:02 +01:00
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)
2009-09-11 14:10:16 +02:00
2012-09-24 22:17:56 +02:00
@classmethod
2016-05-31 12:55:54 +02:00
def _login_ldap(cls, login, parameters):
if 'password' not in parameters:
2020-03-17 23:33:10 +01:00
msg = gettext('res.msg_user_password', login=login)
2016-05-31 12:55:54 +02:00
raise LoginException('password', msg, type='password')
password = parameters['password']
2009-09-11 14:10:16 +02:00
try:
2016-06-03 16:40:43 +02:00
server = ldap_server()
if server:
2015-03-22 03:18:59 +01:00
uid = config.get(section, 'uid', default='uid')
2016-06-03 16:40:43 +02:00
users = cls.ldap_search_user(login, server, attrs=[uid])
2014-10-06 22:30:55 +02:00
if users and len(users) == 1:
[(dn, attrs)] = users
2022-10-10 21:18:17 +02:00
with ldap3.Connection(
server, dn, password,
auto_bind=ldap3.AUTO_BIND_NONE) as con:
if server.ssl is False and server.tls is not None:
con.start_tls()
if (password and con.bind()):
# Use ldap uid so we always get the right case
login = attrs.get(uid, [login])[0]
user_id = cls._get_login(login)[0]
if user_id:
return user_id
elif config.getboolean(section, 'create_user'):
user, = cls.create([{
'name': login,
'login': login,
}])
return user.id
2017-01-14 20:54:20 +01:00
except LDAPException:
2015-03-02 23:40:37 +01:00
logger.error('LDAPError when login', exc_info=True)