ldap: use connection ping only in case of single server specifed.

This commit is contained in:
Marcin Kuzminski 2018-05-04 16:13:31 +02:00
parent e46713343e
commit 12d382294e
3 changed files with 96 additions and 57 deletions

View file

@ -21,7 +21,8 @@
"""
Authentication modules
"""
import socket
import string
import colander
import copy
import logging
@ -31,14 +32,13 @@ import warnings
import functools
from pyramid.threadlocal import get_current_registry
from zope.cachedescriptors.property import Lazy as LazyProperty
from rhodecode.authentication.interface import IAuthnPluginRegistry
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.lib import caches
from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
from rhodecode.lib.utils2 import safe_int
from rhodecode.lib.utils2 import safe_str
from rhodecode.lib.utils2 import safe_int, safe_str
from rhodecode.lib.exceptions import LdapConnectionError
from rhodecode.model.db import User
from rhodecode.model.meta import Session
from rhodecode.model.settings import SettingsModel
@ -556,6 +556,63 @@ class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
return auth
class AuthLdapBase(object):
@classmethod
def _build_servers(cls, ldap_server_type, ldap_server, port):
def host_resolver(host, port, full_resolve=True):
"""
Main work for this function is to prevent ldap connection issues,
and detect them early using a "greenified" sockets
"""
host = host.strip()
if not full_resolve:
return '{}:{}'.format(host, port)
log.debug('LDAP: Resolving IP for LDAP host %s', host)
try:
ip = socket.gethostbyname(host)
log.debug('Got LDAP server %s ip %s', host, ip)
except Exception:
raise LdapConnectionError(
'Failed to resolve host: `{}`'.format(host))
log.debug('LDAP: Checking if IP %s is accessible', ip)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, int(port)))
s.shutdown(socket.SHUT_RD)
except Exception:
raise LdapConnectionError(
'Failed to connect to host: `{}:{}`'.format(host, port))
return '{}:{}'.format(host, port)
if len(ldap_server) == 1:
# in case of single server use resolver to detect potential
# connection issues
full_resolve = True
else:
full_resolve = False
return ', '.join(
["{}://{}".format(
ldap_server_type,
host_resolver(host, port, full_resolve=full_resolve))
for host in ldap_server])
@classmethod
def _get_server_list(cls, servers):
return map(string.strip, servers.split(','))
@classmethod
def get_uid(cls, username, server_addresses):
uid = username
for server_addr in server_addresses:
uid = chop_at(username, "@%s" % server_addr)
return uid
def loadplugin(plugin_id):
"""
Loads and returns an instantiated authentication plugin.
@ -613,7 +670,7 @@ def authenticate(username, password, environ=None, auth_type=None,
authn_registry = get_authn_registry(registry)
plugins_to_check = authn_registry.get_plugins_for_authentication()
log.debug('Starting ordered authentication chain using %s plugins',
plugins_to_check)
[x.name for x in plugins_to_check])
for plugin in plugins_to_check:
plugin.set_auth_type(auth_type)
plugin.set_calling_scope_repo(acl_repo_name)

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2012-2018 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

View file

@ -22,16 +22,13 @@
RhodeCode authentication plugin for LDAP
"""
import socket
import re
import colander
import logging
import traceback
import string
import colander
from rhodecode.translation import _
from rhodecode.authentication.base import (
RhodeCodeExternalAuthPlugin, chop_at, hybrid_property)
RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property)
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
from rhodecode.authentication.routes import AuthnPluginResourceBase
from rhodecode.lib.colander_utils import strip_whitespace
@ -199,40 +196,7 @@ class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
widget='string')
class AuthLdap(object):
def _build_servers(self):
def host_resolver(host, port):
"""
Main work for this function is to prevent ldap connection issues,
and detect them early using a "greenified" sockets
"""
host = host.strip()
log.info('Resolving LDAP host %s', host)
try:
ip = socket.gethostbyname(host)
log.info('Got LDAP server %s ip %s', host, ip)
except Exception:
raise LdapConnectionError(
'Failed to resolve host: `{}`'.format(host))
log.info('Checking LDAP IP access %s', ip)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, int(port)))
s.shutdown(socket.SHUT_RD)
except Exception:
raise LdapConnectionError(
'Failed to connect to host: `{}:{}`'.format(host, port))
return '{}:{}'.format(host, port)
port = self.LDAP_SERVER_PORT
return ', '.join(
["{}://{}".format(
self.ldap_server_type, host_resolver(host, port))
for host in self.SERVER_ADDRESSES])
class AuthLdap(AuthLdapBase):
def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
@ -255,8 +219,9 @@ class AuthLdap(object):
OPT_X_TLS_DEMAND = 2
self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
OPT_X_TLS_DEMAND)
self.LDAP_SERVER = server
# split server into list
self.SERVER_ADDRESSES = server.split(',')
self.SERVER_ADDRESSES = self._get_server_list(server)
self.LDAP_SERVER_PORT = port
# USE FOR READ ONLY BIND TO LDAP SERVER
@ -264,13 +229,12 @@ class AuthLdap(object):
self.LDAP_BIND_DN = safe_str(bind_dn)
self.LDAP_BIND_PASS = safe_str(bind_pass)
self.LDAP_SERVER = self._build_servers()
self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
self.BASE_DN = safe_str(base_dn)
self.LDAP_FILTER = safe_str(ldap_filter)
def _get_ldap_conn(self):
log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER)
if self.debug:
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
@ -284,7 +248,10 @@ class AuthLdap(object):
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
# init connection now
ldap_conn = ldap.initialize(self.LDAP_SERVER)
ldap_servers = self._build_servers(
self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
log.debug('initializing LDAP connection to:%s', ldap_servers)
ldap_conn = ldap.initialize(ldap_servers)
ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
ldap_conn.timeout = self.timeout
@ -304,12 +271,6 @@ class AuthLdap(object):
return ldap_conn
def get_uid(self, username):
uid = username
for server_addr in self.SERVER_ADDRESSES:
uid = chop_at(username, "@%s" % server_addr)
return uid
def fetch_attrs_from_simple_bind(self, server, dn, username, password):
try:
log.debug('Trying simple bind with %s', dn)
@ -335,7 +296,9 @@ class AuthLdap(object):
:param password: password
"""
uid = self.get_uid(username)
uid = self.get_uid(username, self.SERVER_ADDRESSES)
user_attrs = {}
dn = ''
if not password:
msg = "Authenticating user %s with blank password not allowed"
@ -349,8 +312,8 @@ class AuthLdap(object):
ldap_conn = self._get_ldap_conn()
filter_ = '(&%s(%s=%s))' % (
self.LDAP_FILTER, self.attr_login, username)
log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
filter_, self.LDAP_SERVER)
log.debug(
"Authenticating %r filter %s", self.BASE_DN, filter_)
lobjects = ldap_conn.search_ext_s(
self.BASE_DN, self.SEARCH_SCOPE, filter_)