浏览代码

HUE-1609 [core] LDAP backend and import should be case insensitive when searching for users

Abraham Elmahrek 12 年之前
父节点
当前提交
d18497e701

+ 29 - 1
apps/useradmin/src/useradmin/ldap_access.py

@@ -23,6 +23,8 @@ import ldap.filter
 import logging
 import re
 
+from django.contrib.auth.models import User
+
 import desktop.conf
 from desktop.lib.python_util import CaseInsensitiveDict
 
@@ -57,6 +59,32 @@ def get_ldap_username(username, nt_domain):
   else:
     return username
 
+
+def get_ldap_user_kwargs(username):
+  if desktop.conf.LDAP.IGNORE_USERNAME_CASE.get():
+    return {
+      'username__iexact': username
+    }
+  else:
+    return {
+      'username': username
+    }
+
+
+def get_ldap_user(username):
+  username_kwargs = get_ldap_user_kwargs(username)
+  return User.objects.get(**username_kwargs)
+
+
+def get_or_create_ldap_user(username):
+  username_kwargs = get_ldap_user_kwargs(username)
+  users = User.objects.filter(**username_kwargs)
+  if users.exists():
+    return User.objects.get(**username_kwargs), False
+  else:
+    return User.objects.create(username=username), True
+
+
 class LdapConnection(object):
   """
   Constructor creates LDAP connection. Contains methods
@@ -137,7 +165,7 @@ class LdapConnection(object):
 
           ldap_info = {
             'dn': dn,
-            'name': data[user_name_attr][0]
+            'username': data[user_name_attr][0]
           }
 
           if 'givenName' in data:

+ 26 - 2
apps/useradmin/src/useradmin/tests.py

@@ -81,12 +81,13 @@ class LdapTestConnection(object):
     self._instance.groups[group]['posix_members'].remove(user)
 
   def find_users(self, username_pattern, search_attr=None, user_name_attr=None, find_by_dn=False, scope=ldap.SCOPE_SUBTREE):
-    """ Returns info for a particular user """
+    """ Returns info for a particular user via a case insensitive search """
     if find_by_dn:
       data = filter(lambda attrs: attrs['dn'] == username_pattern, self._instance.users.values())
     else:
       username_pattern = "^%s$" % username_pattern.replace('.','\\.').replace('*', '.*')
-      usernames = filter(lambda username: re.match(username_pattern, username), self._instance.users.keys())
+      username_fsm = re.compile(username_pattern, flags=re.I)
+      usernames = filter(lambda username: username_fsm.match(username), self._instance.users.keys())
       data = [self._instance.users.get(username) for username in usernames]
     return data
 
@@ -658,6 +659,16 @@ def test_useradmin_ldap_user_integration():
   assert_equal(get_profile(curly).creation_method, str(UserProfile.CreationMethod.EXTERNAL))
   assert_equal(2, curly.groups.all().count(), curly.groups.all())
 
+  reset_all_users()
+  reset_all_groups()
+
+  # Test import case sensitivity
+  reset = desktop.conf.LDAP.IGNORE_USERNAME_CASE.set_for_testing(True)
+  import_ldap_users('Lårry', sync_groups=False, import_by_dn=False)
+  assert_false(User.objects.filter(username='Lårry').exists())
+  assert_true(User.objects.filter(username='lårry').exists())
+  reset()
+
 
 def test_add_ldap_users():
   URL = reverse(add_ldap_users)
@@ -683,6 +694,19 @@ def test_add_ldap_users():
   response = c.post(URL, dict(username_pattern='*r*', password1='test', password2='test'))
   assert_true('/useradmin/users' in response['Location'], response)
 
+  # Test ignore case
+  reset = desktop.conf.LDAP.IGNORE_USERNAME_CASE.set_for_testing(True)
+  User.objects.filter(username='moe').delete()
+  assert_false(User.objects.filter(username='Moe').exists())
+  assert_false(User.objects.filter(username='moe').exists())
+  response = c.post(URL, dict(username_pattern='Moe', password1='test', password2='test'))
+  assert_true('Location' in response, response)
+  assert_true('/useradmin/users' in response['Location'], response)
+  assert_false(User.objects.filter(username='Moe').exists())
+  assert_true(User.objects.filter(username='moe').exists())
+  reset()
+
+
 def test_add_ldap_groups():
   URL = reverse(add_ldap_groups)
 

+ 3 - 3
apps/useradmin/src/useradmin/views.py

@@ -516,7 +516,7 @@ def _import_ldap_users_info(user_info, sync_groups=False, import_by_dn=False):
   """
   imported_users = []
   for ldap_info in user_info:
-    user, created = User.objects.get_or_create(username=ldap_info['username'])
+    user, created = ldap_access.get_or_create_ldap_user(username=ldap_info['username'])
     profile = get_profile(user)
     if not created and profile.creation_method == str(UserProfile.CreationMethod.HUE):
       # This is a Hue user, and shouldn't be overwritten
@@ -622,7 +622,7 @@ def _import_ldap_groups(groupname_pattern, import_members=False, recursive_impor
         else:
           for ldap_info in user_info:
             try:
-              user = User.objects.get(username=ldap_info['username'])
+              user = ldap_access.get_ldap_user(username=ldap_info['username'])
               users.append(user)
             except User.DoesNotExist:
               pass
@@ -650,7 +650,7 @@ def _import_ldap_groups(groupname_pattern, import_members=False, recursive_impor
         else:
           for ldap_info in user_info:
             try:
-              user = User.objects.get(username=ldap_info['username'])
+              user = ldap_access.get_ldap_user(username=ldap_info['username'])
               users.append(user)
             except User.DoesNotExist:
               pass

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -146,6 +146,9 @@
     # For use when using LdapBackend for Hue authentication
     ## create_users_on_login = true
 
+    # Ignore the case of usernames when searching for existing users in Hue.
+    ## ignore_username_case=false
+
     # Use search bind authentication.
     ## search_bind_authentication=true
 

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -155,6 +155,9 @@
     # For use when using LdapBackend for Hue authentication
     ## create_users_on_login = true
 
+    # Ignore the case of usernames when searching for existing users in Hue.
+    ## ignore_username_case=false
+
     # Use search bind authentication.
     ## search_bind_authentication=true
 

+ 17 - 4
desktop/core/src/desktop/auth/backend.py

@@ -37,6 +37,7 @@ from django.utils.importlib import import_module
 from django.core.exceptions import ImproperlyConfigured
 from useradmin.models import get_profile, get_default_user_group, UserProfile
 from useradmin.views import import_ldap_users
+from useradmin import ldap_access
 
 import pam
 from django_auth_ldap.backend import LDAPBackend, ldap_settings
@@ -283,7 +284,17 @@ class LdapBackend(object):
   """
   def __init__(self):
     # Delegate to django_auth_ldap.LDAPBackend
-    self._backend = LDAPBackend()
+    class _LDAPBackend(LDAPBackend):
+      def get_or_create_user(self, username, ldap_user):
+        if desktop.conf.LDAP.IGNORE_USERNAME_CASE.get():
+          try:
+            return User.objects.get(username__iexact=username), False
+          except User.DoesNotExist:
+            return User.objects.get_or_create(username=username)
+        else:
+          return User.objects.get_or_create(username=username)
+
+    self._backend = _LDAPBackend()
 
     ldap_settings.AUTH_LDAP_SERVER_URI = desktop.conf.LDAP.LDAP_URL.get()
     if ldap_settings.AUTH_LDAP_SERVER_URI is None:
@@ -325,20 +336,22 @@ class LdapBackend(object):
       ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
 
   def authenticate(self, username=None, password=None):
+    username_filter_kwargs = ldap_access.get_ldap_user_kwargs(username)
+
     # Do this check up here, because the auth call creates a django user upon first login per user
     is_super = False
     if not UserProfile.objects.filter(creation_method=str(UserProfile.CreationMethod.EXTERNAL)).exists():
       # If there are no LDAP users already in the system, the first one will
       # become a superuser
       is_super = True
-    elif User.objects.filter(username=username).exists():
+    elif User.objects.filter(**username_filter_kwargs).exists():
       # If the user already exists, we shouldn't change its superuser
       # privileges. However, if there's a naming conflict with a non-external
       # user, we should do the safe thing and turn off superuser privs.
-      existing_user = User.objects.get(username=username)
+      existing_user = User.objects.get(**username_filter_kwargs)
       existing_profile = get_profile(existing_user)
       if existing_profile.creation_method == str(UserProfile.CreationMethod.EXTERNAL):
-        is_super = User.objects.get(username=username).is_superuser
+        is_super = User.objects.get(**username_filter_kwargs).is_superuser
     elif not desktop.conf.LDAP.CREATE_USERS_ON_LOGIN.get():
       return None
 

+ 4 - 0
desktop/core/src/desktop/conf.py

@@ -393,6 +393,10 @@ LDAP = ConfigSection(
       help=_("Create users when they login with their LDAP credentials."),
       type=coerce_bool,
       default=True),
+    IGNORE_USERNAME_CASE = Config("ignore_username_case",
+      help=_("Ignore the case of usernames when searching for existing users in Hue."),
+      type=coerce_bool,
+      default=False),
 
     USERS = ConfigSection(
       key="users",