##// END OF EJS Templates
ldap: use connection ping only in case of single server specifed.
marcink -
r2736:f1074c0b stable
parent child Browse files
Show More
@@ -21,7 +21,8 b''
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24
24 import socket
25 import string
25 import colander
26 import colander
26 import copy
27 import copy
27 import logging
28 import logging
@@ -31,14 +32,13 b' import warnings'
31 import functools
32 import functools
32
33
33 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import caches
38 from rhodecode.lib import caches
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.exceptions import LdapConnectionError
42 from rhodecode.model.db import User
42 from rhodecode.model.db import User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
@@ -556,6 +556,63 b' class RhodeCodeExternalAuthPlugin(RhodeC'
556 return auth
556 return auth
557
557
558
558
559 class AuthLdapBase(object):
560
561 @classmethod
562 def _build_servers(cls, ldap_server_type, ldap_server, port):
563 def host_resolver(host, port, full_resolve=True):
564 """
565 Main work for this function is to prevent ldap connection issues,
566 and detect them early using a "greenified" sockets
567 """
568 host = host.strip()
569 if not full_resolve:
570 return '{}:{}'.format(host, port)
571
572 log.debug('LDAP: Resolving IP for LDAP host %s', host)
573 try:
574 ip = socket.gethostbyname(host)
575 log.debug('Got LDAP server %s ip %s', host, ip)
576 except Exception:
577 raise LdapConnectionError(
578 'Failed to resolve host: `{}`'.format(host))
579
580 log.debug('LDAP: Checking if IP %s is accessible', ip)
581 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
582 try:
583 s.connect((ip, int(port)))
584 s.shutdown(socket.SHUT_RD)
585 except Exception:
586 raise LdapConnectionError(
587 'Failed to connect to host: `{}:{}`'.format(host, port))
588
589 return '{}:{}'.format(host, port)
590
591 if len(ldap_server) == 1:
592 # in case of single server use resolver to detect potential
593 # connection issues
594 full_resolve = True
595 else:
596 full_resolve = False
597
598 return ', '.join(
599 ["{}://{}".format(
600 ldap_server_type,
601 host_resolver(host, port, full_resolve=full_resolve))
602 for host in ldap_server])
603
604 @classmethod
605 def _get_server_list(cls, servers):
606 return map(string.strip, servers.split(','))
607
608 @classmethod
609 def get_uid(cls, username, server_addresses):
610 uid = username
611 for server_addr in server_addresses:
612 uid = chop_at(username, "@%s" % server_addr)
613 return uid
614
615
559 def loadplugin(plugin_id):
616 def loadplugin(plugin_id):
560 """
617 """
561 Loads and returns an instantiated authentication plugin.
618 Loads and returns an instantiated authentication plugin.
@@ -613,7 +670,7 b' def authenticate(username, password, env'
613 authn_registry = get_authn_registry(registry)
670 authn_registry = get_authn_registry(registry)
614 plugins_to_check = authn_registry.get_plugins_for_authentication()
671 plugins_to_check = authn_registry.get_plugins_for_authentication()
615 log.debug('Starting ordered authentication chain using %s plugins',
672 log.debug('Starting ordered authentication chain using %s plugins',
616 plugins_to_check)
673 [x.name for x in plugins_to_check])
617 for plugin in plugins_to_check:
674 for plugin in plugins_to_check:
618 plugin.set_auth_type(auth_type)
675 plugin.set_auth_type(auth_type)
619 plugin.set_calling_scope_repo(acl_repo_name)
676 plugin.set_calling_scope_repo(acl_repo_name)
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -22,16 +22,13 b''
22 RhodeCode authentication plugin for LDAP
22 RhodeCode authentication plugin for LDAP
23 """
23 """
24
24
25 import socket
26 import re
27 import colander
28 import logging
25 import logging
29 import traceback
26 import traceback
30 import string
31
27
28 import colander
32 from rhodecode.translation import _
29 from rhodecode.translation import _
33 from rhodecode.authentication.base import (
30 from rhodecode.authentication.base import (
34 RhodeCodeExternalAuthPlugin, chop_at, hybrid_property)
31 RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
32 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
33 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
34 from rhodecode.lib.colander_utils import strip_whitespace
@@ -199,40 +196,7 b' class LdapSettingsSchema(AuthnPluginSett'
199 widget='string')
196 widget='string')
200
197
201
198
202 class AuthLdap(object):
199 class AuthLdap(AuthLdapBase):
203
204 def _build_servers(self):
205 def host_resolver(host, port):
206 """
207 Main work for this function is to prevent ldap connection issues,
208 and detect them early using a "greenified" sockets
209 """
210 host = host.strip()
211
212 log.info('Resolving LDAP host %s', host)
213 try:
214 ip = socket.gethostbyname(host)
215 log.info('Got LDAP server %s ip %s', host, ip)
216 except Exception:
217 raise LdapConnectionError(
218 'Failed to resolve host: `{}`'.format(host))
219
220 log.info('Checking LDAP IP access %s', ip)
221 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
222 try:
223 s.connect((ip, int(port)))
224 s.shutdown(socket.SHUT_RD)
225 except Exception:
226 raise LdapConnectionError(
227 'Failed to connect to host: `{}:{}`'.format(host, port))
228
229 return '{}:{}'.format(host, port)
230
231 port = self.LDAP_SERVER_PORT
232 return ', '.join(
233 ["{}://{}".format(
234 self.ldap_server_type, host_resolver(host, port))
235 for host in self.SERVER_ADDRESSES])
236
200
237 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
201 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
238 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
202 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
@@ -255,8 +219,9 b' class AuthLdap(object):'
255 OPT_X_TLS_DEMAND = 2
219 OPT_X_TLS_DEMAND = 2
256 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
220 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
257 OPT_X_TLS_DEMAND)
221 OPT_X_TLS_DEMAND)
222 self.LDAP_SERVER = server
258 # split server into list
223 # split server into list
259 self.SERVER_ADDRESSES = server.split(',')
224 self.SERVER_ADDRESSES = self._get_server_list(server)
260 self.LDAP_SERVER_PORT = port
225 self.LDAP_SERVER_PORT = port
261
226
262 # USE FOR READ ONLY BIND TO LDAP SERVER
227 # USE FOR READ ONLY BIND TO LDAP SERVER
@@ -264,13 +229,12 b' class AuthLdap(object):'
264
229
265 self.LDAP_BIND_DN = safe_str(bind_dn)
230 self.LDAP_BIND_DN = safe_str(bind_dn)
266 self.LDAP_BIND_PASS = safe_str(bind_pass)
231 self.LDAP_BIND_PASS = safe_str(bind_pass)
267 self.LDAP_SERVER = self._build_servers()
232
268 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
233 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
269 self.BASE_DN = safe_str(base_dn)
234 self.BASE_DN = safe_str(base_dn)
270 self.LDAP_FILTER = safe_str(ldap_filter)
235 self.LDAP_FILTER = safe_str(ldap_filter)
271
236
272 def _get_ldap_conn(self):
237 def _get_ldap_conn(self):
273 log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER)
274
238
275 if self.debug:
239 if self.debug:
276 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
240 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
@@ -284,7 +248,10 b' class AuthLdap(object):'
284 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
248 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
285
249
286 # init connection now
250 # init connection now
287 ldap_conn = ldap.initialize(self.LDAP_SERVER)
251 ldap_servers = self._build_servers(
252 self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
253 log.debug('initializing LDAP connection to:%s', ldap_servers)
254 ldap_conn = ldap.initialize(ldap_servers)
288 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
255 ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
289 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
256 ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
290 ldap_conn.timeout = self.timeout
257 ldap_conn.timeout = self.timeout
@@ -304,12 +271,6 b' class AuthLdap(object):'
304
271
305 return ldap_conn
272 return ldap_conn
306
273
307 def get_uid(self, username):
308 uid = username
309 for server_addr in self.SERVER_ADDRESSES:
310 uid = chop_at(username, "@%s" % server_addr)
311 return uid
312
313 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
274 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
314 try:
275 try:
315 log.debug('Trying simple bind with %s', dn)
276 log.debug('Trying simple bind with %s', dn)
@@ -335,7 +296,9 b' class AuthLdap(object):'
335 :param password: password
296 :param password: password
336 """
297 """
337
298
338 uid = self.get_uid(username)
299 uid = self.get_uid(username, self.SERVER_ADDRESSES)
300 user_attrs = {}
301 dn = ''
339
302
340 if not password:
303 if not password:
341 msg = "Authenticating user %s with blank password not allowed"
304 msg = "Authenticating user %s with blank password not allowed"
@@ -349,8 +312,8 b' class AuthLdap(object):'
349 ldap_conn = self._get_ldap_conn()
312 ldap_conn = self._get_ldap_conn()
350 filter_ = '(&%s(%s=%s))' % (
313 filter_ = '(&%s(%s=%s))' % (
351 self.LDAP_FILTER, self.attr_login, username)
314 self.LDAP_FILTER, self.attr_login, username)
352 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
315 log.debug(
353 filter_, self.LDAP_SERVER)
316 "Authenticating %r filter %s", self.BASE_DN, filter_)
354 lobjects = ldap_conn.search_ext_s(
317 lobjects = ldap_conn.search_ext_s(
355 self.BASE_DN, self.SEARCH_SCOPE, filter_)
318 self.BASE_DN, self.SEARCH_SCOPE, filter_)
356
319
General Comments 0
You need to be logged in to leave comments. Login now