##// END OF EJS Templates
AD fix when search could return empty dn
marcink -
r1444:d17aa797 beta
parent child Browse files
Show More
@@ -1,144 +1,147 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode authentication library for LDAP
6 RhodeCode authentication library for LDAP
7
7
8 :created_on: Created on Nov 17, 2010
8 :created_on: Created on Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
28 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
29 LdapPasswordError
29 LdapPasswordError
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 try:
34 try:
35 import ldap
35 import ldap
36 except ImportError:
36 except ImportError:
37 # means that python-ldap is not installed
37 # means that python-ldap is not installed
38 pass
38 pass
39
39
40
40
41 class AuthLdap(object):
41 class AuthLdap(object):
42
42
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 search_scope='SUBTREE',
46 search_scope='SUBTREE',
47 attr_login='uid'):
47 attr_login='uid'):
48 self.ldap_version = ldap_version
48 self.ldap_version = ldap_version
49 ldap_server_type = 'ldap'
49 ldap_server_type = 'ldap'
50
50
51 self.TLS_KIND = tls_kind
51 self.TLS_KIND = tls_kind
52
52
53 if self.TLS_KIND == 'LDAPS':
53 if self.TLS_KIND == 'LDAPS':
54 port = port or 689
54 port = port or 689
55 ldap_server_type = ldap_server_type + 's'
55 ldap_server_type = ldap_server_type + 's'
56
56
57 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
57 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
58 self.LDAP_SERVER_ADDRESS = server
58 self.LDAP_SERVER_ADDRESS = server
59 self.LDAP_SERVER_PORT = port
59 self.LDAP_SERVER_PORT = port
60
60
61 #USE FOR READ ONLY BIND TO LDAP SERVER
61 #USE FOR READ ONLY BIND TO LDAP SERVER
62 self.LDAP_BIND_DN = bind_dn
62 self.LDAP_BIND_DN = bind_dn
63 self.LDAP_BIND_PASS = bind_pass
63 self.LDAP_BIND_PASS = bind_pass
64
64
65 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
65 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
66 self.LDAP_SERVER_ADDRESS,
66 self.LDAP_SERVER_ADDRESS,
67 self.LDAP_SERVER_PORT)
67 self.LDAP_SERVER_PORT)
68
68
69 self.BASE_DN = base_dn
69 self.BASE_DN = base_dn
70 self.LDAP_FILTER = ldap_filter
70 self.LDAP_FILTER = ldap_filter
71 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
71 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
72 self.attr_login = attr_login
72 self.attr_login = attr_login
73
73
74 def authenticate_ldap(self, username, password):
74 def authenticate_ldap(self, username, password):
75 """Authenticate a user via LDAP and return his/her LDAP properties.
75 """Authenticate a user via LDAP and return his/her LDAP properties.
76
76
77 Raises AuthenticationError if the credentials are rejected, or
77 Raises AuthenticationError if the credentials are rejected, or
78 EnvironmentError if the LDAP server can't be reached.
78 EnvironmentError if the LDAP server can't be reached.
79
79
80 :param username: username
80 :param username: username
81 :param password: password
81 :param password: password
82 """
82 """
83
83
84 from rhodecode.lib.helpers import chop_at
84 from rhodecode.lib.helpers import chop_at
85
85
86 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
86 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
87
87
88 if "," in username:
88 if "," in username:
89 raise LdapUsernameError("invalid character in username: ,")
89 raise LdapUsernameError("invalid character in username: ,")
90 try:
90 try:
91 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
91 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
92 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
92 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
93 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
93 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
94 ldap.set_option(ldap.OPT_TIMEOUT, 20)
94 ldap.set_option(ldap.OPT_TIMEOUT, 20)
95 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
95 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
96 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
96 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
97 if self.TLS_KIND != 'PLAIN':
97 if self.TLS_KIND != 'PLAIN':
98 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
98 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
99 server = ldap.initialize(self.LDAP_SERVER)
99 server = ldap.initialize(self.LDAP_SERVER)
100 if self.ldap_version == 2:
100 if self.ldap_version == 2:
101 server.protocol = ldap.VERSION2
101 server.protocol = ldap.VERSION2
102 else:
102 else:
103 server.protocol = ldap.VERSION3
103 server.protocol = ldap.VERSION3
104
104
105 if self.TLS_KIND == 'START_TLS':
105 if self.TLS_KIND == 'START_TLS':
106 server.start_tls_s()
106 server.start_tls_s()
107
107
108 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
108 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
109 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
109 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
110
110
111 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
111 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
112 username)
112 username)
113 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
113 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
114 filt, self.LDAP_SERVER)
114 filt, self.LDAP_SERVER)
115 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
115 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
116 filt)
116 filt)
117
117
118 if not lobjects:
118 if not lobjects:
119 raise ldap.NO_SUCH_OBJECT()
119 raise ldap.NO_SUCH_OBJECT()
120
120
121 for (dn, _attrs) in lobjects:
121 for (dn, _attrs) in lobjects:
122 if dn is None:
123 continue
124
122 try:
125 try:
123 server.simple_bind_s(dn, password)
126 server.simple_bind_s(dn, password)
124 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
127 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
125 '(objectClass=*)')[0][1]
128 '(objectClass=*)')[0][1]
126 break
129 break
127
130
128 except ldap.INVALID_CREDENTIALS, e:
131 except ldap.INVALID_CREDENTIALS, e:
129 log.debug("LDAP rejected password for user '%s' (%s): %s",
132 log.debug("LDAP rejected password for user '%s' (%s): %s",
130 uid, username, dn)
133 uid, username, dn)
131
134
132 else:
135 else:
133 log.debug("No matching LDAP objects for authentication "
136 log.debug("No matching LDAP objects for authentication "
134 "of '%s' (%s)", uid, username)
137 "of '%s' (%s)", uid, username)
135 raise LdapPasswordError()
138 raise LdapPasswordError()
136
139
137 except ldap.NO_SUCH_OBJECT, e:
140 except ldap.NO_SUCH_OBJECT, e:
138 log.debug("LDAP says no such user '%s' (%s)", uid, username)
141 log.debug("LDAP says no such user '%s' (%s)", uid, username)
139 raise LdapUsernameError()
142 raise LdapUsernameError()
140 except ldap.SERVER_DOWN, e:
143 except ldap.SERVER_DOWN, e:
141 raise LdapConnectionError("LDAP can't access "
144 raise LdapConnectionError("LDAP can't access "
142 "authentication server")
145 "authentication server")
143
146
144 return (dn, attrs)
147 return (dn, attrs)
General Comments 0
You need to be logged in to leave comments. Login now