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. |
|
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, |
|
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( |
|
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 |
|
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