text: #!/usr/bin/env python
  # Licensed to Cloudera, Inc. under one
  # or more contributor license agreements.  See the NOTICE file
  # distributed with this work for additional information
  # regarding copyright ownership.  Cloudera, Inc. licenses this file
  # to you under the Apache License, Version 2.0 (the
  # "License"); you may not use this file except in compliance
  # with the License.  You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  """
  This module provides access to LDAP servers, along with some basic functionality required for Hue and
  User Admin to work seamlessly with LDAP.
  """
  import desktop.conf
  import ldap
  import ldap.filter
  
  import logging
  LOG = logging.getLogger(__name__)
  
  CACHED_LDAP_CONN = None
  
  def get_connection():
    global CACHED_LDAP_CONN
    if CACHED_LDAP_CONN is not None:
      return CACHED_LDAP_CONN
  
    ldap_url = desktop.conf.LDAP.LDAP_URL.get()
    nt_domain = desktop.conf.LDAP.NT_DOMAIN.get()
    username = desktop.conf.LDAP.BIND_DN.get()
    password = desktop.conf.LDAP.BIND_PASSWORD.get()
    ldap_cert = desktop.conf.LDAP.LDAP_CERT.get()
  
    if ldap_url is None:
      raise Exception('No LDAP URL was specified')
  
    return LdapConnection(ldap_url, get_ldap_username(username, nt_domain),
                          password, ldap_cert)
  
  def get_ldap_username(username, nt_domain):
    if nt_domain:
      return '%s@%s' % (username, nt_domain)
    else:
      return username
  
  class LdapConnection(object):
    """
    Constructor creates LDAP connection. Contains methods
    to easily query an LDAP server.
    """
  
    def __init__(self, ldap_url, bind_user=None, bind_password=None, cert_file=None):
      """
      Constructor initializes the LDAP connection
      """
      if cert_file is not None:
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
        ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, cert_file)
  
      ldap.set_option(ldap.OPT_REFERRALS, 0)
  
      self.ldap_handle = ldap.initialize(ldap_url)
  
      if bind_user is not None:
        try:
          self.ldap_handle.simple_bind_s(bind_user, bind_password)
        except:
          raise RuntimeError("Failed to bind to LDAP server as user %s" %
              (bind_user,))
      else:
        try:
          # Do anonymous bind
          self.ldap_handle.simple_bind_s('','')
        except:
          raise RuntimeError("Failed to bind to LDAP server anonymously")
  
    def find_user(self, username, find_by_dn=False):
      """
      LDAP search helper method finding users. This supports searching for users
      by distinguished name, or the configured username attribute.
      """
      base_dn = self._get_root_dn()
      scope = ldap.SCOPE_SUBTREE
  
      user_filter = desktop.conf.LDAP.USERS.USER_FILTER.get()
      if not user_filter.startswith('('):
        user_filter = '(' + user_filter + ')'
      user_name_attr = desktop.conf.LDAP.USERS.USER_NAME_ATTR.get()
  
      if find_by_dn:
        sanitized_name = ldap.filter.escape_filter_chars(username)
        user_name_filter = '(distinguishedName=' + sanitized_name + ')'
      else:
        sanitized_name = ldap.filter.escape_filter_chars(username)
        user_name_filter = '(' + user_name_attr + '=' + sanitized_name + ')'
      ldap_filter = '(&' + user_filter + user_name_filter + ')'
  
      ldap_result_id = self.ldap_handle.search(base_dn, scope, ldap_filter)
      result_type, result_data = self.ldap_handle.result(ldap_result_id)
      if result_type == ldap.RES_SEARCH_RESULT and result_data[0][0] is not None:
        data = result_data[0][1]
        user_info = { 'username': data[user_name_attr][0] }
  
        if 'givenName' in data:
          user_info['first'] = data['givenName'][0]
        if 'sn' in data:
          user_info['last'] = data['sn'][0]
        if 'mail' in data:
          user_info['email'] = data['mail'][0]
  
        return user_info
  
      return None
  
    def find_group(self, groupname, find_by_dn=False):
      """
      LDAP search helper method for finding groups
      """
      base_dn = self._get_root_dn()
      scope = ldap.SCOPE_SUBTREE
  
      group_filter = desktop.conf.LDAP.GROUPS.GROUP_FILTER.get()
      if not group_filter.startswith('('):
        group_filter = '(' + group_filter + ')'
      group_name_attr = desktop.conf.LDAP.GROUPS.GROUP_NAME_ATTR.get()
  
      if find_by_dn:
        sanitized_name = ldap.filter.escape_filter_chars(groupname)
        group_name_filter = '(distinguishedName=' + sanitized_name + ')'
      else:
        sanitized_name = ldap.filter.escape_filter_chars(groupname)
        group_name_filter = '(' + group_name_attr + '=' + sanitized_name + ')'
      ldap_filter = '(&' + group_filter + group_name_filter + ')'
  
      ldap_result_id = self.ldap_handle.search(base_dn, scope, ldap_filter)
      result_type, result_data = self.ldap_handle.result(ldap_result_id)
      if result_type == ldap.RES_SEARCH_RESULT and result_data[0][0] is not None:
        data = result_data[0][1]
        group_info = { 'name': data[group_name_attr][0] }
  
        member_attr = desktop.conf.LDAP.GROUPS.GROUP_MEMBER_ATTR.get()
        if member_attr in data:
          group_info['members'] = data[member_attr]
        else:
          group_info['members'] = []
  
        return group_info
  
      return None
  
    def _get_root_dn(self):
      """
      Returns the configured base DN (DC=desktop,DC=local).
      """
      return desktop.conf.LDAP.BASE_DN.get()
  