diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py --- a/rhodecode/lib/auth_ldap.py +++ b/rhodecode/lib/auth_ldap.py @@ -1,8 +1,15 @@ -#!/usr/bin/env python -# encoding: utf-8 -# ldap authentication lib -# Copyright (C) 2009-2011 Marcin Kuzminski -# +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.changelog + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + RhodeCode authentication library for LDAP + + :created_on: Created on Nov 17, 2010 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski + :license: GPLv3, see COPYING for more details. +""" # 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 @@ -15,30 +22,39 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Created on Nov 17, 2010 -@author: marcink -""" - -from rhodecode.lib.exceptions import * import logging +from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \ + LdapPasswordError + log = logging.getLogger(__name__) + try: import ldap except ImportError: + # means that python-ldap is not installed pass + class AuthLdap(object): def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - use_ldaps=False, ldap_version=3): + tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, + ldap_filter='(&(objectClass=user)(!(objectClass=computer)))', + search_scope='SUBTREE', + attr_login='uid'): self.ldap_version = ldap_version - if use_ldaps: + ldap_server_type = 'ldap' + + self.TLS_KIND = tls_kind + + if self.TLS_KIND == 'LDAPS': port = port or 689 - self.LDAP_USE_LDAPS = use_ldaps + ldap_server_type = ldap_server_type + 's' + + self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert] self.LDAP_SERVER_ADDRESS = server self.LDAP_SERVER_PORT = port @@ -46,20 +62,21 @@ class AuthLdap(object): self.LDAP_BIND_DN = bind_dn self.LDAP_BIND_PASS = bind_pass - ldap_server_type = 'ldap' - if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's' self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type, self.LDAP_SERVER_ADDRESS, self.LDAP_SERVER_PORT) self.BASE_DN = base_dn + self.LDAP_FILTER = ldap_filter + self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope] + self.attr_login = attr_login def authenticate_ldap(self, username, password): """Authenticate a user via LDAP and return his/her LDAP properties. - + Raises AuthenticationError if the credentials are rejected, or EnvironmentError if the LDAP server can't be reached. - + :param username: username :param password: password """ @@ -72,31 +89,59 @@ class AuthLdap(object): raise LdapUsernameError("invalid character in username: ,") try: ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts') + 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) ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10) + ldap.set_option(ldap.OPT_TIMELIMIT, 15) + if self.TLS_KIND != 'PLAIN': + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) server = ldap.initialize(self.LDAP_SERVER) if self.ldap_version == 2: server.protocol = ldap.VERSION2 else: server.protocol = ldap.VERSION3 + if self.TLS_KIND == 'START_TLS': + server.start_tls_s() + if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) - dn = self.BASE_DN % {'user':uid} - log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER) - server.simple_bind_s(dn, password) + filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, + username) + log.debug("Authenticating %r filt %s at %s", self.BASE_DN, + filt, self.LDAP_SERVER) + lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, + filt) + + if not lobjects: + raise ldap.NO_SUCH_OBJECT() + + for (dn, _attrs) in lobjects: + if dn is None: + continue - properties = server.search_s(dn, ldap.SCOPE_SUBTREE) - if not properties: - raise ldap.NO_SUCH_OBJECT() + try: + server.simple_bind_s(dn, password) + attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, + '(objectClass=*)')[0][1] + break + + except ldap.INVALID_CREDENTIALS, e: + log.debug("LDAP rejected password for user '%s' (%s): %s", + uid, username, dn) + + else: + log.debug("No matching LDAP objects for authentication " + "of '%s' (%s)", uid, username) + raise LdapPasswordError() + except ldap.NO_SUCH_OBJECT, e: log.debug("LDAP says no such user '%s' (%s)", uid, username) raise LdapUsernameError() - except ldap.INVALID_CREDENTIALS, e: - log.debug("LDAP rejected password for user '%s' (%s)", uid, username) - raise LdapPasswordError() except ldap.SERVER_DOWN, e: - raise LdapConnectionError("LDAP can't access authentication server") + raise LdapConnectionError("LDAP can't access " + "authentication server") - return properties[0] - + return (dn, attrs)