Show More
@@ -21,7 +21,8 b'' | |||
|
21 | 21 | """ |
|
22 | 22 | Authentication modules |
|
23 | 23 | """ |
|
24 | ||
|
24 | import socket | |
|
25 | import string | |
|
25 | 26 | import colander |
|
26 | 27 | import copy |
|
27 | 28 | import logging |
@@ -31,14 +32,13 b' import warnings' | |||
|
31 | 32 | import functools |
|
32 | 33 | |
|
33 | 34 | from pyramid.threadlocal import get_current_registry |
|
34 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
|
35 | 35 | |
|
36 | 36 | from rhodecode.authentication.interface import IAuthnPluginRegistry |
|
37 | 37 | from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase |
|
38 | 38 | from rhodecode.lib import caches |
|
39 | 39 | from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt |
|
40 | from rhodecode.lib.utils2 import safe_int | |
|
41 |
from rhodecode.lib. |
|
|
40 | from rhodecode.lib.utils2 import safe_int, safe_str | |
|
41 | from rhodecode.lib.exceptions import LdapConnectionError | |
|
42 | 42 | from rhodecode.model.db import User |
|
43 | 43 | from rhodecode.model.meta import Session |
|
44 | 44 | from rhodecode.model.settings import SettingsModel |
@@ -556,6 +556,63 b' class RhodeCodeExternalAuthPlugin(RhodeC' | |||
|
556 | 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 | 616 | def loadplugin(plugin_id): |
|
560 | 617 | """ |
|
561 | 618 | Loads and returns an instantiated authentication plugin. |
@@ -613,7 +670,7 b' def authenticate(username, password, env' | |||
|
613 | 670 | authn_registry = get_authn_registry(registry) |
|
614 | 671 | plugins_to_check = authn_registry.get_plugins_for_authentication() |
|
615 | 672 | log.debug('Starting ordered authentication chain using %s plugins', |
|
616 | plugins_to_check) | |
|
673 | [x.name for x in plugins_to_check]) | |
|
617 | 674 | for plugin in plugins_to_check: |
|
618 | 675 | plugin.set_auth_type(auth_type) |
|
619 | 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 | 22 | RhodeCode authentication plugin for LDAP |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | import socket | |
|
26 | import re | |
|
27 | import colander | |
|
28 | 25 | import logging |
|
29 | 26 | import traceback |
|
30 | import string | |
|
31 | 27 | |
|
28 | import colander | |
|
32 | 29 | from rhodecode.translation import _ |
|
33 | 30 | from rhodecode.authentication.base import ( |
|
34 |
RhodeCodeExternalAuthPlugin, |
|
|
31 | RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property) | |
|
35 | 32 | from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase |
|
36 | 33 | from rhodecode.authentication.routes import AuthnPluginResourceBase |
|
37 | 34 | from rhodecode.lib.colander_utils import strip_whitespace |
@@ -199,40 +196,7 b' class LdapSettingsSchema(AuthnPluginSett' | |||
|
199 | 196 | widget='string') |
|
200 | 197 | |
|
201 | 198 | |
|
202 |
class AuthLdap( |
|
|
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]) | |
|
199 | class AuthLdap(AuthLdapBase): | |
|
236 | 200 | |
|
237 | 201 | def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', |
|
238 | 202 | tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, |
@@ -255,8 +219,9 b' class AuthLdap(object):' | |||
|
255 | 219 | OPT_X_TLS_DEMAND = 2 |
|
256 | 220 | self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, |
|
257 | 221 | OPT_X_TLS_DEMAND) |
|
222 | self.LDAP_SERVER = server | |
|
258 | 223 | # split server into list |
|
259 |
self.SERVER_ADDRESSES = server |
|
|
224 | self.SERVER_ADDRESSES = self._get_server_list(server) | |
|
260 | 225 | self.LDAP_SERVER_PORT = port |
|
261 | 226 | |
|
262 | 227 | # USE FOR READ ONLY BIND TO LDAP SERVER |
@@ -264,13 +229,12 b' class AuthLdap(object):' | |||
|
264 | 229 | |
|
265 | 230 | self.LDAP_BIND_DN = safe_str(bind_dn) |
|
266 | 231 | self.LDAP_BIND_PASS = safe_str(bind_pass) |
|
267 | self.LDAP_SERVER = self._build_servers() | |
|
232 | ||
|
268 | 233 | self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) |
|
269 | 234 | self.BASE_DN = safe_str(base_dn) |
|
270 | 235 | self.LDAP_FILTER = safe_str(ldap_filter) |
|
271 | 236 | |
|
272 | 237 | def _get_ldap_conn(self): |
|
273 | log.debug('initializing LDAP connection to:%s', self.LDAP_SERVER) | |
|
274 | 238 | |
|
275 | 239 | if self.debug: |
|
276 | 240 | ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) |
@@ -284,7 +248,10 b' class AuthLdap(object):' | |||
|
284 | 248 | ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) |
|
285 | 249 | |
|
286 | 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 | 255 | ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout) |
|
289 | 256 | ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout) |
|
290 | 257 | ldap_conn.timeout = self.timeout |
@@ -304,12 +271,6 b' class AuthLdap(object):' | |||
|
304 | 271 | |
|
305 | 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 | 274 | def fetch_attrs_from_simple_bind(self, server, dn, username, password): |
|
314 | 275 | try: |
|
315 | 276 | log.debug('Trying simple bind with %s', dn) |
@@ -335,7 +296,9 b' class AuthLdap(object):' | |||
|
335 | 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 | 303 | if not password: |
|
341 | 304 | msg = "Authenticating user %s with blank password not allowed" |
@@ -349,8 +312,8 b' class AuthLdap(object):' | |||
|
349 | 312 | ldap_conn = self._get_ldap_conn() |
|
350 | 313 | filter_ = '(&%s(%s=%s))' % ( |
|
351 | 314 | self.LDAP_FILTER, self.attr_login, username) |
|
352 | log.debug("Authenticating %r filter %s at %s", self.BASE_DN, | |
|
353 | filter_, self.LDAP_SERVER) | |
|
315 | log.debug( | |
|
316 | "Authenticating %r filter %s", self.BASE_DN, filter_) | |
|
354 | 317 | lobjects = ldap_conn.search_ext_s( |
|
355 | 318 | self.BASE_DN, self.SEARCH_SCOPE, filter_) |
|
356 | 319 |
General Comments 0
You need to be logged in to leave comments.
Login now