Use trytond configuration for connection

issue4179
review5631002
This commit is contained in:
Cédric Krier
2014-09-20 13:58:31 +02:00
parent 2f93feb20b
commit 5375a62089
9 changed files with 140 additions and 133 deletions
+2
View File
@@ -1,3 +1,5 @@
* Use trytond configuration for connection
Version 3.2.0 - 2014-04-21
* Bug fixes (see mercurial logs for details)
-2
View File
@@ -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')
-36
View File
@@ -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
-13
View File
@@ -1,13 +0,0 @@
<?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>
<record model="ir.ui.view" id="connection_view_form">
<field name="model">ldap.connection</field>
<field name="type">form</field>
<field name="inherit" ref="ldap_connection.connection_view_form"/>
<field name="name">connection_form</field>
</record>
</data>
</tryton>
+37
View File
@@ -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
+61 -46
View File
@@ -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
+40 -11
View File
@@ -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()
-3
View File
@@ -2,7 +2,4 @@
version=3.3.0
depends:
ir
ldap_connection
res
xml:
connection.xml
-22
View File
@@ -1,22 +0,0 @@
<?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. -->
<data>
<xpath expr="/form/group[@id='buttons']" position="before">
<separator string="Authentication" id="authentication"
colspan="6"/>
<label name="auth_uid"/>
<field name="auth_uid"/>
<label name="auth_base_dn"/>
<field name="auth_base_dn"/>
<newline/>
<label name="auth_require_filter"/>
<field name="auth_require_filter"/>
<label name="auth_scope"/>
<field name="auth_scope"/>
<newline/>
<label name="auth_create_user"/>
<field name="auth_create_user"/>
<newline/>
</xpath>
</data>