diff --git a/CHANGELOG b/CHANGELOG
index 8f03a85e0b..53265610c8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,5 @@
+* Use trytond configuration for connection
+
Version 3.2.0 - 2014-04-21
* Bug fixes (see mercurial logs for details)
diff --git a/__init__.py b/__init__.py
index 2bc2b00bf0..c79ab3e3b2 100644
--- a/__init__.py
+++ b/__init__.py
@@ -2,12 +2,10 @@
#this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
-from .connection import *
from .res import *
def register():
Pool.register(
- Connection,
User,
module='ldap_authentication', type_='model')
diff --git a/connection.py b/connection.py
deleted file mode 100644
index 812013250c..0000000000
--- a/connection.py
+++ /dev/null
@@ -1,36 +0,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.
-from trytond.model import fields
-from trytond.pool import PoolMeta
-
-__all__ = ['Connection']
-__metaclass__ = PoolMeta
-
-
-class Connection:
- __name__ = 'ldap.connection'
-
- auth_uid = fields.Char('UID', help='UID Attribute for authentication',
- required=True)
- auth_base_dn = fields.Char('Base DN', help='Base DN for authentication',
- required=True)
- auth_require_filter = fields.Char('Require Filter')
- auth_scope = fields.Selection([
- ('base', 'Base'),
- ('onelevel', 'OneLevel'),
- ('subtree', 'Subtree'),
- ], 'Scope', required=True)
- auth_create_user = fields.Boolean('Create User',
- help='Create user if not in database')
-
- @staticmethod
- def default_auth_uid():
- return 'uid'
-
- @staticmethod
- def default_auth_scope():
- return 'base'
-
- @staticmethod
- def default_auth_create_user():
- return False
diff --git a/connection.xml b/connection.xml
deleted file mode 100644
index 1b5bb22668..0000000000
--- a/connection.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- ldap.connection
- form
-
- connection_form
-
-
-
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000000..f5c48a74be
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,37 @@
+LDAP Authentication Module
+##########################
+
+The LDAP authentication module allows to authenticate users via a LDAP server.
+
+The configuration of the LDAP connection is set in the `ldap_authentication`
+section.
+
+Configuration
+*************
+
+uri
+---
+
+The LDAP URL to use to connect to the server following the RFC-2255_.
+
+bind_pass
+---------
+
+The LDAP password used to bind if needed.
+
+active_directory
+----------------
+
+A boolean to set if the LDAP server is an Active Directory.
+
+uid
+---
+
+The UID Attribute for authentication (default is `uid`).
+
+create_user
+-----------
+
+A boolean to create user if not in the database.
+
+.. _RFC-2255: http://tools.ietf.org/html/rfc2255
diff --git a/res.py b/res.py
index 7bca8866c4..30049d9833 100644
--- a/res.py
+++ b/res.py
@@ -1,15 +1,56 @@
#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:
@@ -24,26 +65,27 @@ class User:
})
@staticmethod
- def ldap_search_user(login, con, connection, attrs=None):
+ 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(connection.auth_scope)
- if connection.auth_require_filter:
- filter = '(&(%s=%s)%s)' % (connection.auth_uid, login,
- connection.auth_require_filter)
+ }.get(scope)
+ uid = config.get(section, 'uid', 'uid')
+ if filter_:
+ filter_ = '(&(%s=%s)%s)' % (uid, login, filter_)
else:
- filter = '(%s=%s)' % (connection.auth_uid, login)
+ filter_ = '(%s=%s)' % (uid, login)
- result = con.search_s(connection.auth_base_dn or '', scope,
- filter, attrs)
- if connection.active_directory:
+ 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')
@@ -51,20 +93,11 @@ class User:
@classmethod
def _check_passwd_ldap_user(cls, logins):
- Connection = Pool().get('ldap.connection')
- connection, = Connection.search([], limit=1)
find = False
try:
- con = ldap.initialize(connection.uri)
- if connection.active_directory:
- con.set_option(ldap.OPT_REFERRALS, 0)
- if connection.secure == 'tls':
- con.start_tls_s()
- if connection.bind_dn:
- con.simple_bind_s(connection.bind_dn, connection.bind_pass)
+ con = ldap_connection()
for login in logins:
- if cls.ldap_search_user(login,
- con, connection, attrs=[]):
+ if cls.ldap_search_user(login, con, attrs=[]):
find = True
break
except ldap.LDAPError, e:
@@ -94,20 +127,12 @@ class User:
@classmethod
def set_preferences(cls, values, old_password=False):
- Connection = Pool().get('ldap.connection')
if 'password' in values:
- connection, = Connection.search([], limit=1)
try:
- con = ldap.initialize(connection.uri)
- if connection.active_directory:
- con.set_option(ldap.OPT_REFERRALS, 0)
- if connection.secure == 'tls':
- con.start_tls_s()
- if connection.bind_dn:
- con.simple_bind_s(connection.bind_dn, connection.bind_pass)
+ con = ldap_connection()
user = cls(Transaction().user)
- users = cls.ldap_search_user(user.login, con,
- connection, attrs=[str(connection.auth_uid)])
+ 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):
@@ -123,20 +148,11 @@ class User:
@classmethod
def get_login(cls, login, password):
pool = Pool()
- Connection = pool.get('ldap.connection')
LoginAttempt = pool.get('res.user.login.attempt')
- connection, = Connection.search([], limit=1)
try:
- con = ldap.initialize(connection.uri or '')
- if connection.active_directory:
- con.set_option(ldap.OPT_REFERRALS, 0)
- if connection.secure == 'tls':
- con.start_tls_s()
- if connection.bind_dn:
- con.simple_bind_s(connection.bind_dn or '',
- connection.bind_pass or '')
- users = cls.ldap_search_user(login, con, connection,
- attrs=[str(connection.auth_uid)])
+ con = ldap_connection()
+ 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):
@@ -144,10 +160,9 @@ class User:
if user_id:
LoginAttempt.remove(login)
return user_id
- elif connection.auth_create_user:
+ elif config.getboolean(section, 'create_user'):
user, = cls.create([{
- 'name': attrs.get(str(connection.auth_uid),
- [login])[0],
+ 'name': attrs.get(uid, [login])[0],
'login': login,
}])
return user.id
diff --git a/tests/test_ldap_authentication.py b/tests/test_ldap_authentication.py
index 6c163f284a..f42b5fa31a 100644
--- a/tests/test_ldap_authentication.py
+++ b/tests/test_ldap_authentication.py
@@ -6,9 +6,14 @@ from mock import patch
import ldap
import trytond.tests.test_tryton
-from trytond.tests.test_tryton import test_view, test_depends
+from trytond.tests.test_tryton import test_depends
from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT
from trytond.transaction import Transaction
+from trytond.config import config
+
+from trytond.modules.ldap_authentication.res import parse_ldap_url
+
+section = 'ldap_authentication'
class LDAPAuthenticationTestCase(unittest.TestCase):
@@ -16,10 +21,11 @@ class LDAPAuthenticationTestCase(unittest.TestCase):
def setUp(self):
trytond.tests.test_tryton.install_module('ldap_authentication')
+ config.add_section(section)
+ config.set(section, 'uri', 'ldap://localhost/dc=tryton,dc=org')
- def test0005views(self):
- 'Test views'
- test_view('ldap_authentication')
+ def tearDown(self):
+ config.remove_section(section)
def test0006depends(self):
'Test depends'
@@ -29,11 +35,6 @@ class LDAPAuthenticationTestCase(unittest.TestCase):
'Test User.get_login'
with Transaction().start(DB_NAME, USER, context=CONTEXT):
User = POOL.get('res.user')
- Connection = POOL.get('ldap.connection')
-
- connection = Connection(server='localhost',
- auth_base_dn='dc=tryton,dc=org')
- connection.save()
@patch.object(ldap, 'initialize')
@patch.object(User, 'ldap_search_user')
@@ -55,13 +56,41 @@ class LDAPAuthenticationTestCase(unittest.TestCase):
self.assertFalse(get_login('foo', 'bar', True))
# Test create new user
- connection.auth_create_user = True
- connection.save()
+ config.set(section, 'create_user', 'True')
user_id = get_login('foo', 'bar', True)
foo, = User.search([('login', '=', 'foo')])
self.assertEqual(user_id, foo.id)
self.assertEqual(foo.name, 'foo')
+ 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']})
+
def suite():
suite = trytond.tests.test_tryton.suite()
diff --git a/tryton.cfg b/tryton.cfg
index e3582f56d9..b07f9f1bff 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -2,7 +2,4 @@
version=3.3.0
depends:
ir
- ldap_connection
res
-xml:
- connection.xml
diff --git a/view/connection_form.xml b/view/connection_form.xml
deleted file mode 100644
index 690fed00ad..0000000000
--- a/view/connection_form.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-