|
|
@@ -0,0 +1,159 @@
|
|
|
+#!/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()
|
|
|
+
|
|
|
+ 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 'email' in data:
|
|
|
+ user_info['email'] = data['email'][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()
|