Initial import from Docker volume

This commit is contained in:
root
2025-12-26 13:11:43 +00:00
commit 4998dc066a
13336 changed files with 1767801 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import res
def register():
Pool.register(
res.User,
module='ldap_authentication', type_='model')

Binary file not shown.

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "No podeu canviar la contrasenya del usuari de LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Das Passwort des LDAP Benutzers \"%(user)s\" kann nicht geändert werden."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "No se puede cambiar la contraseña del usuario de LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "LDAP kasutaja \"%(user)s\" parooli ei saa muuta."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "شما نمی توانید رمز عبور LDAP کاربر:\"%(user)s\" را تغییر دهید."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,9 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""
"Vous ne pouvez pas changer le mot de passe de l'utilisateur LDAP "
"« %(user)s »."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Anda tidak dapat mengubah kata sandi pengguna LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Non puoi cambiare la password dell'utente LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "U kunt het wachtwoord van LDAP-gebruiker \"%(user)s\" niet wijzigen."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Nie możesz zmienić hasła użytkownika LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Nu puteți modifica parola utilizatorului LDAP \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Gesla LDAP uporabnika \"%(user)s\" ni mogoče spremeniti."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr ""

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "Ви не можете змінити пароль LDAP-користувача \"%(user)s\"."

View File

@@ -0,0 +1,7 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.message,text:msg_ldap_user_change_password"
msgid "You cannot change the password of LDAP user \"%(user)s\"."
msgstr "你不能更改LDAP用户 \"%(user)s\"的密码."

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_ldap_user_change_password">
<field name="text">You cannot change the password of LDAP user "%(user)s".</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,171 @@
# 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 ssl
import urllib.parse
import ldap3
from ldap3.core.exceptions import LDAPException
from trytond.config import config, parse_uri
from trytond.exceptions import LoginException
from trytond.i18n import gettext
from trytond.model.exceptions import AccessError
from trytond.pool import PoolMeta
from trytond.transaction import without_check_access
logger = logging.getLogger(__name__)
section = 'ldap_authentication'
# Old version of urlparse doesn't parse query for ldap
# see http://bugs.python.org/issue9374
if 'ldap' not in urllib.parse.uses_query:
urllib.parse.uses_query.append('ldap')
def parse_ldap_url(uri):
unquote = urllib.parse.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 = urllib.parse.parse_qs(extensions)
return (uri, dn, unquote(attributes), unquote(scope), unquote(filter_),
extensions)
def ldap_server():
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)
else:
scheme, port = 'ldap', 389
tls = None
if 'tls' in uri.scheme:
tls = ldap3.Tls(validate=ssl.CERT_REQUIRED)
return ldap3.Server('%s://%s:%s' % (
scheme, uri.hostname, uri.port or port), tls=tls)
class User(metaclass=PoolMeta):
__name__ = 'res.user'
@staticmethod
def ldap_search_user(login, server, attrs=None):
'''
Return the result of a ldap search for the login using the ldap
server.
The attributes values defined in attrs will be return.
'''
_, dn, _, scope, filter_, extensions = parse_ldap_url(
config.get(section, 'uri'))
scope = {
'base': ldap3.BASE,
'onelevel': ldap3.LEVEL,
'one': ldap3.LEVEL,
'subtree': ldap3.SUBTREE,
'sub': ldap3.SUBTREE,
}[scope]
uid = config.get(section, 'uid', default='uid')
if filter_:
filter_ = '(&(%s=%s)%s)' % (uid, login, filter_)
else:
filter_ = '(%s=%s)' % (uid, login)
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')
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:
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')
return [(e.entry_dn, e.entry_attributes_as_dict)
for e in result]
@classmethod
@without_check_access
def _check_passwd_ldap_user(cls, logins):
find = False
try:
server = ldap_server()
if not server:
return
for login in logins:
if cls.ldap_search_user(login, server, attrs=[]):
find = True
break
except LDAPException:
logger.error('LDAPError when checking password', exc_info=True)
if find:
raise AccessError(
gettext('ldap_authentication.msg_ldap_user_change_password',
user=login))
@classmethod
def create(cls, vlist):
tocheck = []
for values in vlist:
if values.get('password') and 'login' in values:
tocheck.append(values['login'])
if tocheck:
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 _login_ldap(cls, login, parameters):
if 'password' not in parameters:
msg = gettext('res.msg_user_password', login=login)
raise LoginException('password', msg, type='password')
password = parameters['password']
try:
server = ldap_server()
if server:
uid = config.get(section, 'uid', default='uid')
users = cls.ldap_search_user(login, server, attrs=[uid])
if users and len(users) == 1:
[(dn, attrs)] = users
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
except LDAPException:
logger.error('LDAPError when login', exc_info=True)

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,227 @@
# 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 sys
import unittest
from unittest.mock import ANY, MagicMock, Mock, patch
import ldap3
from trytond.config import config
from trytond.modules.ldap_authentication.res import ldap_server, parse_ldap_url
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
section = 'ldap_authentication'
class LDAPAuthenticationTestCase(ModuleTestCase):
'Test LDAPAuthentication module'
module = 'ldap_authentication'
def setUp(self):
super(LDAPAuthenticationTestCase, self).setUp()
methods = config.get('session', 'authentications', default='')
config.set('session', 'authentications', 'ldap')
self.addCleanup(config.set, 'session', 'authentications', methods)
config.add_section(section)
self.addCleanup(config.remove_section, section)
def _get_login(
self, uri='ldap://localhost/dc=tryton,dc=org', start_tls=False):
pool = Pool()
User = pool.get('res.user')
config.set(section, 'uri', uri)
@patch.object(ldap3, 'Connection')
@patch.object(User, 'ldap_search_user')
def get_login(login, password, find, ldap_search_user, Connection):
con = Connection.return_value = MagicMock()
con.__enter__.return_value = con
con.bind.return_value = bool(find)
if find:
ldap_search_user.return_value = [('dn', {'uid': [find]})]
else:
ldap_search_user.return_value = None
user_id = User.get_login(login, {
'password': password,
})
if find:
Connection.assert_called_with(
ANY, ANY, password, auto_bind=ldap3.AUTO_BIND_NONE)
if start_tls:
con.start_tls.assert_called()
else:
con.start_tls.assert_not_called()
con.bind.assert_called()
return user_id
return get_login
@with_transaction()
def test_user_get_login_existing_user(self):
"Test User.get_login with existing user"
pool = Pool()
User = pool.get('res.user')
user, = User.search([('login', '=', 'admin')])
get_login = self._get_login()
self.assertEqual(get_login('admin', 'admin', 'admin'), user.id)
self.assertEqual(get_login('AdMiN', 'admin', 'admin'), user.id)
@with_transaction()
def test_user_get_login_unknown_user(self):
"test User.get_login with unknown user"
get_login = self._get_login()
self.assertFalse(get_login('foo', 'bar', None))
self.assertFalse(get_login('foo', 'bar', 'foo'))
@with_transaction()
def test_user_get_login_create_user(self):
"Test User.get_login with user to create"
pool = Pool()
User = pool.get('res.user')
config.set(section, 'create_user', 'True')
get_login = self._get_login()
user_id = get_login('foo', 'bar', 'foo')
foo, = User.search([('login', '=', 'foo')])
self.assertEqual(user_id, foo.id)
self.assertEqual(foo.name, 'foo')
@with_transaction()
def test_user_get_login_create_user_case(self):
"Test User.get_login with user to create with different case"
pool = Pool()
User = pool.get('res.user')
config.set(section, 'create_user', 'True')
get_login = self._get_login()
user_id = get_login('BaR', 'foo', 'bar')
bar, = User.search([('login', '=', 'bar')])
self.assertEqual(user_id, bar.id)
self.assertEqual(bar.name, 'bar')
@with_transaction()
def test_user_get_login_with_tls(self):
"Test User.get_login with TLS"
pool = Pool()
User = pool.get('res.user')
user, = User.search([('login', '=', 'admin')])
get_login = self._get_login(
'ldap+tls://localhost/dc=tryton,dc=org', start_tls=True)
self.assertEqual(get_login('admin', 'admin', 'admin'), user.id)
@with_transaction()
def test_user_get_login_with_ssl(self):
"Test User.get_login with SSL"
pool = Pool()
User = pool.get('res.user')
user, = User.search([('login', '=', 'admin')])
get_login = self._get_login('ldaps://localhost/dc=tryton,dc=org')
self.assertEqual(get_login('admin', 'admin', 'admin'), user.id)
def test_parse_ldap_url(self):
'Test parse_ldap_url'
self.assertEqual(
parse_ldap_url('ldap:///o=University%20of%20Michigan,c=US')[1],
'o=University of Michigan,c=US')
self.assertEqual(
parse_ldap_url(
'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US'
)[1],
'o=University of Michigan,c=US')
self.assertEqual(
parse_ldap_url(
'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,'
'c=US?postalAddress')[2],
'postalAddress')
self.assertEqual(
parse_ldap_url(
'ldap://host.com:6666/o=University%20of%20Michigan,'
'c=US??sub?(cn=Babs%20Jensen)')[3:5],
('sub', '(cn=Babs Jensen)'))
self.assertEqual(
parse_ldap_url(
'ldap:///??sub??bindname=cn=Manager%2co=Foo')[5],
{'bindname': ['cn=Manager,o=Foo']})
self.assertEqual(
parse_ldap_url(
'ldap:///??sub??!bindname=cn=Manager%2co=Foo')[5],
{'!bindname': ['cn=Manager,o=Foo']})
@unittest.skipIf(
sys.version_info < (3, 8), "call_args does not have args nor kwargs")
def test_ldap_server(self):
"Test ldap_server"
for uri, (host, tls) in [
('ldap://localhost/dc=tryton,dc=org',
('ldap://localhost:389', None)),
('ldaps://localhost/dc=tryton,dc=org',
('ldaps://localhost:636', True)),
('ldap+tls://localhost/dc=tryton,dc=org',
('ldap://localhost:389', True)),
]:
config.set(section, 'uri', uri)
with patch('ldap3.Server') as Server:
ldap_server()
self.assertEqual(Server.call_args.args, (host,))
if tls:
self.assertTrue(Server.call_args.kwargs.get('tls'))
else:
self.assertFalse(Server.call_args.kwargs.get('tls'))
def _ldap_search_user(
self, uri='ldap://localhost/dc=tryton,dc=org',
auto_bind=ldap3.AUTO_BIND_DEFAULT):
pool = Pool()
User = pool.get('res.user')
config.set(section, 'uri', uri)
@patch.object(ldap3, 'Connection')
def ldap_search_user(login, attrs, Connection):
con = Connection.return_value = MagicMock()
con.__enter__.return_value = con
con.entries = [Mock()]
server = ldap_server()
User.ldap_search_user(login, server, attrs=attrs)
Connection.assert_called_with(
ANY, ANY, ANY, auto_bind=auto_bind)
con.search.assert_called()
return ldap_search_user
@with_transaction()
def test_ldap_search_user(self):
"Test User.ldap_search_user"
ldap_search_user = self._ldap_search_user()
ldap_search_user('admin', None)
@with_transaction()
def test_ldap_search_user_with_tls(self):
"Test User.ldap_search_user with TSL"
ldap_search_user = self._ldap_search_user(
'ldap+tls://localhost/dc=tryton,dc=org',
ldap3.AUTO_BIND_TLS_BEFORE_BIND)
ldap_search_user('admin', None)
@with_transaction()
def test_ldap_search_user_with_ssl(self):
"Test User.ldap_search_user with SSL"
ldap_search_user = self._ldap_search_user(
'ldaps://localhost/dc=tryton,dc=org')
ldap_search_user('admin', None)
del ModuleTestCase

View File

@@ -0,0 +1,7 @@
[tryton]
version=7.2.0
depends:
ir
res
xml:
message.xml