# Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
# See LICENSE in the project root for license information.

from __future__ import absolute_import
import ldap
import os


class Authenticator:
    def __init__(self, config):
        root = os.path.abspath('./')
        self.ldap_url = config['auth']['ldap_url']
        self.cert_path = os.path.join(root, config['auth']['ldap_cert_path'])
        self.user_suffix = config['auth']['ldap_user_suffix']
        self.authenticate = self.ldap_auth
        if config.get('debug'):
            self.authenticate = self.debug_auth

    def ldap_auth(self, username, password):
        ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.cert_path)
        connection = ldap.initialize(self.ldap_url)
        connection.set_option(ldap.OPT_REFERRALS, 0)

        try:
            if password:
                connection.simple_bind_s(username + self.user_suffix, password)
            else:
                return False
        except ldap.INVALID_CREDENTIALS:
            return False
        except ldap.SERVER_DOWN:
            return None
        return True

    def debug_auth(self, username, password):
        return True

@@ -204,22 +204,74 @@
 rt l
-dap%0Aimport os%0A
+ogging%0Aimport ldap%0Aimport os%0A%0Alogger = logging.getLogger(__name__)
 %0A%0Acl
@@ -370,153 +370,446 @@
     
-self.ldap_url = config%5B'auth'%5D%5B'ldap_url'%5D%0A        self.cert_path = os.path.join(root, config%5B'auth'%5D%5B'ldap_cert_path'%5D)%0A        self.user_suffix
+if config.get('debug'):%0A            self.authenticate = self.debug_auth%0A            return%0A        self.authenticate = self.ldap_auth%0A%0A        if 'ldap_cert_path' in config:%0A            self.cert_path = os.path.join(root, config%5B'auth'%5D%5B'ldap_cert_path'%5D)%0A            if not os.access(self.cert_path, os.R_OK):%0A                logger.error(%22Failed to read ldap_cert_path certificate%22)%0A                raise IOError%0A%0A        self.bind_user
  = c
@@ -825,28 +825,30 @@
 th'%5D
-%5B
+.get(
 'ldap_
-user_suffix'%5D
+bind_user')
 %0A   
@@ -861,79 +861,199 @@
 elf.
-authenticate = self.ldap_auth%0A        if config.get('debug'):%0A 
+bind_password = config%5B'auth'%5D.get('ldap_bind_password')%0A        self.search_filter = config%5B'auth'%5D.get('ldap_search_filter')%0A%0A        self.ldap_url = config%5B'auth'%5D.get('ldap_url')%0A
         
    s
@@ -1052,46 +1052,124 @@
     
-   
 self.
-authenticate = self.debug_auth
+base_dn = config%5B'auth'%5D.get('ldap_base_dn')%0A%0A        self.user_suffix = config%5B'auth'%5D.get('ldap_user_suffix')
 %0A%0A  
@@ -1396,116 +1396,429 @@
     
-try:%0A            if password:%0A                connection.simple_bind_s(username + self.user_suffix, password
+if not password:%0A            return False%0A%0A        auth_user = username + self.user_suffix%0A        try:%0A            if self.bind_user:%0A                # use search filter to find DN of username%0A                connection.simple_bind_s(self.bind_user, self.bind_password)%0A                sfilter = self.search_filter %25 username%0A                result = connection.search_s(self.base_dn, ldap.SCOPE_SUBTREE, sfilter, %5B'dn'%5D
 )%0A  
@@ -1831,14 +1831,36 @@
     
+ 
-else:%0A
+   if len(result) %3C 1:%0A    
     
@@ -1876,32 +1876,133 @@
    return False%0A
+                auth_user = result%5B0%5D%5B0%5D%0A%0A            connection.simple_bind_s(auth_user, password)%0A%0A
         except l
@@ -2062,24 +2062,25 @@
      except 
+(
 ldap.SERVER_
@@ -2083,17 +2083,84 @@
 VER_DOWN
-:
+, ldap.INVALID_DN_SYNTAX) as err:%0A            logger.warn(%22%25s%22, err)
 %0A       

add support for initial bind user