Use trytond configuration for connection
issue4179 review5631002
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Use trytond configuration for connection
|
||||
|
||||
Version 3.2.0 - 2014-04-21
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -2,7 +2,4 @@
|
||||
version=3.3.0
|
||||
depends:
|
||||
ir
|
||||
ldap_connection
|
||||
res
|
||||
xml:
|
||||
connection.xml
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user