auth_ldap.py
167 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
r1292 | # -*- coding: utf-8 -*- | |||
""" | ||||
rhodecode.controllers.changelog | ||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
RhodeCode authentication library for LDAP | ||||
:created_on: Created on Nov 17, 2010 | ||||
:author: marcink | ||||
r1824 | :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> | |||
r1292 | :license: GPLv3, see COPYING for more details. | |||
""" | ||||
r1206 | # This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU General Public License as published by | ||||
# the Free Software Foundation, either version 3 of the License, or | ||||
# (at your option) any later version. | ||||
r1203 | # | |||
r713 | # 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. | ||||
r1203 | # | |||
r713 | # You should have received a copy of the GNU General Public License | |||
r1206 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
r700 | ||||
r705 | import logging | |||
r1292 | from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \ | |||
Mads Kiilerich
|
r3632 | LdapPasswordError, LdapImportError | ||
r2681 | from rhodecode.lib.utils2 import safe_str | |||
r1292 | ||||
r705 | log = logging.getLogger(__name__) | |||
r700 | ||||
r1292 | ||||
r705 | try: | |||
import ldap | ||||
except ImportError: | ||||
r1292 | # means that python-ldap is not installed | |||
Mads Kiilerich
|
r3632 | ldap = None | ||
r700 | ||||
r1292 | ||||
r705 | class AuthLdap(object): | |||
r700 | ||||
r705 | def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', | |||
r1292 | tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, | |||
Thayne Harbaugh
|
r991 | ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', | ||
r1792 | search_scope='SUBTREE', attr_login='uid'): | |||
Mads Kiilerich
|
r3632 | if ldap is None: | ||
raise LdapImportError | ||||
r705 | self.ldap_version = ldap_version | |||
"Lorenzo M. Catucci"
|
r1290 | ldap_server_type = 'ldap' | ||
self.TLS_KIND = tls_kind | ||||
if self.TLS_KIND == 'LDAPS': | ||||
r705 | port = port or 689 | |||
"Lorenzo M. Catucci"
|
r1290 | ldap_server_type = ldap_server_type + 's' | ||
r1792 | ||||
r1579 | OPT_X_TLS_DEMAND = 2 | |||
r1792 | self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, | |||
r1579 | OPT_X_TLS_DEMAND) | |||
Raoul Thill
|
r2915 | # split server into list | ||
self.LDAP_SERVER_ADDRESS = server.split(',') | ||||
r705 | self.LDAP_SERVER_PORT = port | |||
r700 | ||||
r1690 | # USE FOR READ ONLY BIND TO LDAP SERVER | |||
r2681 | self.LDAP_BIND_DN = safe_str(bind_dn) | |||
self.LDAP_BIND_PASS = safe_str(bind_pass) | ||||
Raoul Thill
|
r2915 | _LDAP_SERVERS = [] | ||
for host in self.LDAP_SERVER_ADDRESS: | ||||
_LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type, | ||||
host.replace(' ', ''), | ||||
self.LDAP_SERVER_PORT)) | ||||
self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS)) | ||||
r2681 | self.BASE_DN = safe_str(base_dn) | |||
self.LDAP_FILTER = safe_str(ldap_filter) | ||||
r1579 | self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) | |||
Thayne Harbaugh
|
r991 | self.attr_login = attr_login | ||
r705 | def authenticate_ldap(self, username, password): | |||
r1792 | """ | |||
Authenticate a user via LDAP and return his/her LDAP properties. | ||||
r1203 | ||||
r705 | Raises AuthenticationError if the credentials are rejected, or | |||
EnvironmentError if the LDAP server can't be reached. | ||||
r1203 | ||||
r705 | :param username: username | |||
:param password: password | ||||
""" | ||||
from rhodecode.lib.helpers import chop_at | ||||
uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS) | ||||
r775 | ||||
Shawn K. O'Shea
|
r1659 | if not password: | ||
r1792 | log.debug("Attempt to authenticate LDAP user " | |||
"with blank password rejected.") | ||||
Shawn K. O'Shea
|
r1659 | raise LdapPasswordError() | ||
r705 | if "," in username: | |||
r713 | raise LdapUsernameError("invalid character in username: ,") | |||
r705 | try: | |||
r1792 | if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): | |||
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, | ||||
r1579 | '/etc/openldap/cacerts') | |||
Thayne Harbaugh
|
r991 | ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) | ||
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) | ||||
ldap.set_option(ldap.OPT_TIMEOUT, 20) | ||||
r705 | ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) | |||
Thayne Harbaugh
|
r991 | ldap.set_option(ldap.OPT_TIMELIMIT, 15) | ||
"Lorenzo M. Catucci"
|
r1290 | if self.TLS_KIND != 'PLAIN': | ||
Thayne Harbaugh
|
r991 | ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) | ||
r705 | server = ldap.initialize(self.LDAP_SERVER) | |||
if self.ldap_version == 2: | ||||
server.protocol = ldap.VERSION2 | ||||
else: | ||||
server.protocol = ldap.VERSION3 | ||||
r700 | ||||
"Lorenzo M. Catucci"
|
r1290 | if self.TLS_KIND == 'START_TLS': | ||
server.start_tls_s() | ||||
r705 | if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: | |||
r2920 | log.debug('Trying simple_bind with password and given DN: %s' | |||
% self.LDAP_BIND_DN) | ||||
r794 | server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) | |||
r700 | ||||
r1792 | filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, | |||
r1292 | username) | |||
r1792 | log.debug("Authenticating %r filter %s at %s", self.BASE_DN, | |||
filter_, self.LDAP_SERVER) | ||||
r1170 | lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, | |||
r1792 | filter_) | |||
Thayne Harbaugh
|
r991 | |||
if not lobjects: | ||||
raise ldap.NO_SUCH_OBJECT() | ||||
r775 | ||||
"Lorenzo M. Catucci"
|
r1287 | for (dn, _attrs) in lobjects: | ||
r1444 | if dn is None: | |||
continue | ||||
Thayne Harbaugh
|
r991 | try: | ||
r1792 | log.debug('Trying simple bind with %s' % dn) | |||
Thayne Harbaugh
|
r991 | server.simple_bind_s(dn, password) | ||
r1292 | attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, | |||
'(objectClass=*)')[0][1] | ||||
Thayne Harbaugh
|
r991 | break | ||
r1792 | except ldap.INVALID_CREDENTIALS: | |||
r1976 | log.debug( | |||
"LDAP rejected password for user '%s' (%s): %s" % ( | ||||
uid, username, dn | ||||
) | ||||
) | ||||
Thayne Harbaugh
|
r991 | |||
r1185 | else: | |||
log.debug("No matching LDAP objects for authentication " | ||||
"of '%s' (%s)", uid, username) | ||||
raise LdapPasswordError() | ||||
Thayne Harbaugh
|
r991 | |||
r1792 | except ldap.NO_SUCH_OBJECT: | |||
r1976 | log.debug("LDAP says no such user '%s' (%s)" % (uid, username)) | |||
r713 | raise LdapUsernameError() | |||
r1792 | except ldap.SERVER_DOWN: | |||
r1292 | raise LdapConnectionError("LDAP can't access " | |||
"authentication server") | ||||
r705 | ||||
Thayne Harbaugh
|
r991 | return (dn, attrs) | ||