##// END OF EJS Templates
fix for issue #277,...
marcink -
r1579:9b80fb89 beta
parent child Browse files
Show More
@@ -1,147 +1,151 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 OPT_X_TLS_DEMAND = 2
58 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
59 OPT_X_TLS_DEMAND)
58 self.LDAP_SERVER_ADDRESS = server
60 self.LDAP_SERVER_ADDRESS = server
59 self.LDAP_SERVER_PORT = port
61 self.LDAP_SERVER_PORT = port
60
62
61 #USE FOR READ ONLY BIND TO LDAP SERVER
63 #USE FOR READ ONLY BIND TO LDAP SERVER
62 self.LDAP_BIND_DN = bind_dn
64 self.LDAP_BIND_DN = bind_dn
63 self.LDAP_BIND_PASS = bind_pass
65 self.LDAP_BIND_PASS = bind_pass
64
66
65 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
67 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
66 self.LDAP_SERVER_ADDRESS,
68 self.LDAP_SERVER_ADDRESS,
67 self.LDAP_SERVER_PORT)
69 self.LDAP_SERVER_PORT)
68
70
69 self.BASE_DN = base_dn
71 self.BASE_DN = base_dn
70 self.LDAP_FILTER = ldap_filter
72 self.LDAP_FILTER = ldap_filter
71 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
73 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
72 self.attr_login = attr_login
74 self.attr_login = attr_login
73
75
74 def authenticate_ldap(self, username, password):
76 def authenticate_ldap(self, username, password):
75 """Authenticate a user via LDAP and return his/her LDAP properties.
77 """Authenticate a user via LDAP and return his/her LDAP properties.
76
78
77 Raises AuthenticationError if the credentials are rejected, or
79 Raises AuthenticationError if the credentials are rejected, or
78 EnvironmentError if the LDAP server can't be reached.
80 EnvironmentError if the LDAP server can't be reached.
79
81
80 :param username: username
82 :param username: username
81 :param password: password
83 :param password: password
82 """
84 """
83
85
84 from rhodecode.lib.helpers import chop_at
86 from rhodecode.lib.helpers import chop_at
85
87
86 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
88 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
87
89
88 if "," in username:
90 if "," in username:
89 raise LdapUsernameError("invalid character in username: ,")
91 raise LdapUsernameError("invalid character in username: ,")
90 try:
92 try:
91 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
93 if hasattr(ldap,'OPT_X_TLS_CACERTDIR'):
94 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
95 '/etc/openldap/cacerts')
92 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
96 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
93 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
97 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
94 ldap.set_option(ldap.OPT_TIMEOUT, 20)
98 ldap.set_option(ldap.OPT_TIMEOUT, 20)
95 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
99 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
96 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
100 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
97 if self.TLS_KIND != 'PLAIN':
101 if self.TLS_KIND != 'PLAIN':
98 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
102 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
99 server = ldap.initialize(self.LDAP_SERVER)
103 server = ldap.initialize(self.LDAP_SERVER)
100 if self.ldap_version == 2:
104 if self.ldap_version == 2:
101 server.protocol = ldap.VERSION2
105 server.protocol = ldap.VERSION2
102 else:
106 else:
103 server.protocol = ldap.VERSION3
107 server.protocol = ldap.VERSION3
104
108
105 if self.TLS_KIND == 'START_TLS':
109 if self.TLS_KIND == 'START_TLS':
106 server.start_tls_s()
110 server.start_tls_s()
107
111
108 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
112 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
109 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
113 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
110
114
111 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
115 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
112 username)
116 username)
113 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
117 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
114 filt, self.LDAP_SERVER)
118 filt, self.LDAP_SERVER)
115 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
119 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
116 filt)
120 filt)
117
121
118 if not lobjects:
122 if not lobjects:
119 raise ldap.NO_SUCH_OBJECT()
123 raise ldap.NO_SUCH_OBJECT()
120
124
121 for (dn, _attrs) in lobjects:
125 for (dn, _attrs) in lobjects:
122 if dn is None:
126 if dn is None:
123 continue
127 continue
124
128
125 try:
129 try:
126 server.simple_bind_s(dn, password)
130 server.simple_bind_s(dn, password)
127 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
131 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
128 '(objectClass=*)')[0][1]
132 '(objectClass=*)')[0][1]
129 break
133 break
130
134
131 except ldap.INVALID_CREDENTIALS, e:
135 except ldap.INVALID_CREDENTIALS, e:
132 log.debug("LDAP rejected password for user '%s' (%s): %s",
136 log.debug("LDAP rejected password for user '%s' (%s): %s",
133 uid, username, dn)
137 uid, username, dn)
134
138
135 else:
139 else:
136 log.debug("No matching LDAP objects for authentication "
140 log.debug("No matching LDAP objects for authentication "
137 "of '%s' (%s)", uid, username)
141 "of '%s' (%s)", uid, username)
138 raise LdapPasswordError()
142 raise LdapPasswordError()
139
143
140 except ldap.NO_SUCH_OBJECT, e:
144 except ldap.NO_SUCH_OBJECT, e:
141 log.debug("LDAP says no such user '%s' (%s)", uid, username)
145 log.debug("LDAP says no such user '%s' (%s)", uid, username)
142 raise LdapUsernameError()
146 raise LdapUsernameError()
143 except ldap.SERVER_DOWN, e:
147 except ldap.SERVER_DOWN, e:
144 raise LdapConnectionError("LDAP can't access "
148 raise LdapConnectionError("LDAP can't access "
145 "authentication server")
149 "authentication server")
146
150
147 return (dn, attrs)
151 return (dn, attrs)
General Comments 0
You need to be logged in to leave comments. Login now