##// END OF EJS Templates
ldap requires only string attributes, fixes #435 ldap-does-not-work-with-non-latin-symbols
marcink -
r2681:8e10ce55 beta
parent child Browse files
Show More
@@ -1,159 +1,160
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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 from rhodecode.lib.utils2 import safe_str
30
31
31 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
32
33
33
34
34 try:
35 try:
35 import ldap
36 import ldap
36 except ImportError:
37 except ImportError:
37 # means that python-ldap is not installed
38 # means that python-ldap is not installed
38 pass
39 pass
39
40
40
41
41 class AuthLdap(object):
42 class AuthLdap(object):
42
43
43 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
44 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
45 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
46 search_scope='SUBTREE', attr_login='uid'):
47 search_scope='SUBTREE', attr_login='uid'):
47 self.ldap_version = ldap_version
48 self.ldap_version = ldap_version
48 ldap_server_type = 'ldap'
49 ldap_server_type = 'ldap'
49
50
50 self.TLS_KIND = tls_kind
51 self.TLS_KIND = tls_kind
51
52
52 if self.TLS_KIND == 'LDAPS':
53 if self.TLS_KIND == 'LDAPS':
53 port = port or 689
54 port = port or 689
54 ldap_server_type = ldap_server_type + 's'
55 ldap_server_type = ldap_server_type + 's'
55
56
56 OPT_X_TLS_DEMAND = 2
57 OPT_X_TLS_DEMAND = 2
57 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
58 OPT_X_TLS_DEMAND)
59 OPT_X_TLS_DEMAND)
59 self.LDAP_SERVER_ADDRESS = server
60 self.LDAP_SERVER_ADDRESS = server
60 self.LDAP_SERVER_PORT = port
61 self.LDAP_SERVER_PORT = port
61
62
62 # USE FOR READ ONLY BIND TO LDAP SERVER
63 # USE FOR READ ONLY BIND TO LDAP SERVER
63 self.LDAP_BIND_DN = bind_dn
64 self.LDAP_BIND_DN = safe_str(bind_dn)
64 self.LDAP_BIND_PASS = bind_pass
65 self.LDAP_BIND_PASS = safe_str(bind_pass)
65
66
66 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
67 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
67 self.LDAP_SERVER_ADDRESS,
68 self.LDAP_SERVER_ADDRESS,
68 self.LDAP_SERVER_PORT)
69 self.LDAP_SERVER_PORT)
69
70
70 self.BASE_DN = base_dn
71 self.BASE_DN = safe_str(base_dn)
71 self.LDAP_FILTER = ldap_filter
72 self.LDAP_FILTER = safe_str(ldap_filter)
72 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
73 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
73 self.attr_login = attr_login
74 self.attr_login = attr_login
74
75
75 def authenticate_ldap(self, username, password):
76 def authenticate_ldap(self, username, password):
76 """
77 """
77 Authenticate a user via LDAP and return his/her LDAP properties.
78 Authenticate a user via LDAP and return his/her LDAP properties.
78
79
79 Raises AuthenticationError if the credentials are rejected, or
80 Raises AuthenticationError if the credentials are rejected, or
80 EnvironmentError if the LDAP server can't be reached.
81 EnvironmentError if the LDAP server can't be reached.
81
82
82 :param username: username
83 :param username: username
83 :param password: password
84 :param password: password
84 """
85 """
85
86
86 from rhodecode.lib.helpers import chop_at
87 from rhodecode.lib.helpers import chop_at
87
88
88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
89 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
89
90
90 if not password:
91 if not password:
91 log.debug("Attempt to authenticate LDAP user "
92 log.debug("Attempt to authenticate LDAP user "
92 "with blank password rejected.")
93 "with blank password rejected.")
93 raise LdapPasswordError()
94 raise LdapPasswordError()
94 if "," in username:
95 if "," in username:
95 raise LdapUsernameError("invalid character in username: ,")
96 raise LdapUsernameError("invalid character in username: ,")
96 try:
97 try:
97 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
98 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
98 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
99 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
99 '/etc/openldap/cacerts')
100 '/etc/openldap/cacerts')
100 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
101 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
101 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
102 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
102 ldap.set_option(ldap.OPT_TIMEOUT, 20)
103 ldap.set_option(ldap.OPT_TIMEOUT, 20)
103 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
104 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
104 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
105 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
105 if self.TLS_KIND != 'PLAIN':
106 if self.TLS_KIND != 'PLAIN':
106 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
107 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
107 server = ldap.initialize(self.LDAP_SERVER)
108 server = ldap.initialize(self.LDAP_SERVER)
108 if self.ldap_version == 2:
109 if self.ldap_version == 2:
109 server.protocol = ldap.VERSION2
110 server.protocol = ldap.VERSION2
110 else:
111 else:
111 server.protocol = ldap.VERSION3
112 server.protocol = ldap.VERSION3
112
113
113 if self.TLS_KIND == 'START_TLS':
114 if self.TLS_KIND == 'START_TLS':
114 server.start_tls_s()
115 server.start_tls_s()
115
116
116 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
117 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
117 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
118 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
118
119
119 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
120 filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
120 username)
121 username)
121 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
122 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
122 filter_, self.LDAP_SERVER)
123 filter_, self.LDAP_SERVER)
123 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
124 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
124 filter_)
125 filter_)
125
126
126 if not lobjects:
127 if not lobjects:
127 raise ldap.NO_SUCH_OBJECT()
128 raise ldap.NO_SUCH_OBJECT()
128
129
129 for (dn, _attrs) in lobjects:
130 for (dn, _attrs) in lobjects:
130 if dn is None:
131 if dn is None:
131 continue
132 continue
132
133
133 try:
134 try:
134 log.debug('Trying simple bind with %s' % dn)
135 log.debug('Trying simple bind with %s' % dn)
135 server.simple_bind_s(dn, password)
136 server.simple_bind_s(dn, password)
136 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
137 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
137 '(objectClass=*)')[0][1]
138 '(objectClass=*)')[0][1]
138 break
139 break
139
140
140 except ldap.INVALID_CREDENTIALS:
141 except ldap.INVALID_CREDENTIALS:
141 log.debug(
142 log.debug(
142 "LDAP rejected password for user '%s' (%s): %s" % (
143 "LDAP rejected password for user '%s' (%s): %s" % (
143 uid, username, dn
144 uid, username, dn
144 )
145 )
145 )
146 )
146
147
147 else:
148 else:
148 log.debug("No matching LDAP objects for authentication "
149 log.debug("No matching LDAP objects for authentication "
149 "of '%s' (%s)", uid, username)
150 "of '%s' (%s)", uid, username)
150 raise LdapPasswordError()
151 raise LdapPasswordError()
151
152
152 except ldap.NO_SUCH_OBJECT:
153 except ldap.NO_SUCH_OBJECT:
153 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
154 log.debug("LDAP says no such user '%s' (%s)" % (uid, username))
154 raise LdapUsernameError()
155 raise LdapUsernameError()
155 except ldap.SERVER_DOWN:
156 except ldap.SERVER_DOWN:
156 raise LdapConnectionError("LDAP can't access "
157 raise LdapConnectionError("LDAP can't access "
157 "authentication server")
158 "authentication server")
158
159
159 return (dn, attrs)
160 return (dn, attrs)
General Comments 0
You need to be logged in to leave comments. Login now