浏览代码

HUE-3054 [useradmin] Raise a popup notification if any exceptions were encountered on batch LDAP import/sync

Jenny Kim 10 年之前
父节点
当前提交
452ce9c
共有 2 个文件被更改,包括 78 次插入38 次删除
  1. 4 0
      apps/useradmin/src/useradmin/test_ldap.py
  2. 74 38
      apps/useradmin/src/useradmin/views.py

+ 4 - 0
apps/useradmin/src/useradmin/test_ldap.py

@@ -572,6 +572,10 @@ class TestUserAdminLdap(BaseUserAdminTests):
       assert_true('Location' in response, response)
       assert_true('Location' in response, response)
       assert_true('/useradmin/groups' in response['Location'])
       assert_true('/useradmin/groups' in response['Location'])
 
 
+      # Test warning notification for failed users on group import
+      response = c.post(URL, dict(server='nonsense', groupname_pattern='TestUsers'), follow=True)
+      assert_true('Failed to import following users: test_toolongusernametoolongusername' in response.content, response.content)
+
       # Test with space
       # Test with space
       response = c.post(URL, dict(server='nonsense', groupname_pattern='Test Administrators'))
       response = c.post(URL, dict(server='nonsense', groupname_pattern='Test Administrators'))
       assert_true('Location' in response, response)
       assert_true('Location' in response, response)

+ 74 - 38
apps/useradmin/src/useradmin/views.py

@@ -24,7 +24,7 @@ import json
 
 
 import ldap
 import ldap
 import ldap_access
 import ldap_access
-from ldap_access import LdapSearchException
+from ldap_access import LdapBindException, LdapSearchException
 
 
 from django.contrib.auth.models import User, Group
 from django.contrib.auth.models import User, Group
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
@@ -429,9 +429,10 @@ def add_ldap_users(request):
       import_by_dn = form.cleaned_data['dn']
       import_by_dn = form.cleaned_data['dn']
       server = form.cleaned_data.get('server')
       server = form.cleaned_data.get('server')
       try:
       try:
+        failed_ldap_users = []
         connection = ldap_access.get_connection_from_server(server)
         connection = ldap_access.get_connection_from_server(server)
-        users = import_ldap_users(connection, username_pattern, False, import_by_dn)
-      except ldap.LDAPError, e:
+        users = import_ldap_users(connection, username_pattern, False, import_by_dn, failed_users=failed_ldap_users)
+      except (ldap.LDAPError, LdapBindException), e:
         LOG.error("LDAP Exception: %s" % e)
         LOG.error("LDAP Exception: %s" % e)
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
       except ValidationError, e:
       except ValidationError, e:
@@ -452,6 +453,11 @@ def add_ldap_users(request):
           'operation': 'ADD_LDAP_USERS',
           'operation': 'ADD_LDAP_USERS',
           'operationText': 'Added/Synced LDAP username(s): %s' % username_pattern
           'operationText': 'Added/Synced LDAP username(s): %s' % username_pattern
         }
         }
+
+        if failed_ldap_users:
+          unique_users = set(failed_ldap_users)
+          request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
+
         return redirect(reverse(list_users))
         return redirect(reverse(list_users))
   else:
   else:
     form = AddLdapUsersForm()
     form = AddLdapUsersForm()
@@ -488,11 +494,12 @@ def add_ldap_groups(request):
       server = form.cleaned_data.get('server')
       server = form.cleaned_data.get('server')
 
 
       try:
       try:
+        failed_ldap_users = []
         connection = ldap_access.get_connection_from_server(server)
         connection = ldap_access.get_connection_from_server(server)
         groups = import_ldap_groups(connection, groupname_pattern, import_members=import_members,
         groups = import_ldap_groups(connection, groupname_pattern, import_members=import_members,
                                     import_members_recursive=import_members_recursive, sync_users=True,
                                     import_members_recursive=import_members_recursive, sync_users=True,
-                                    import_by_dn=import_by_dn)
-      except ldap.LDAPError, e:
+                                    import_by_dn=import_by_dn, failed_users=failed_ldap_users)
+      except (ldap.LDAPError, LdapBindException), e:
         LOG.error(_("LDAP Exception: %s") % e)
         LOG.error(_("LDAP Exception: %s") % e)
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
       except ValidationError, e:
       except ValidationError, e:
@@ -514,6 +521,11 @@ def add_ldap_groups(request):
           'operation': 'ADD_LDAP_GROUPS',
           'operation': 'ADD_LDAP_GROUPS',
           'operationText': 'Added LDAP Group(s): %s' % groupname_pattern
           'operationText': 'Added LDAP Group(s): %s' % groupname_pattern
         }
         }
+
+        if failed_ldap_users:
+          unique_users = set(failed_ldap_users)
+          request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
+
         return redirect(reverse(list_groups))
         return redirect(reverse(list_groups))
       else:
       else:
         errors = form._errors.setdefault('groupname_pattern', ErrorList())
         errors = form._errors.setdefault('groupname_pattern', ErrorList())
@@ -548,23 +560,32 @@ def sync_ldap_users_groups(request):
       server = form.cleaned_data.get('server')
       server = form.cleaned_data.get('server')
       connection = ldap_access.get_connection_from_server(server)
       connection = ldap_access.get_connection_from_server(server)
 
 
-      sync_ldap_users_and_groups(connection, is_ensuring_home_directory, request.fs)
+      failed_ldap_users = []
+
+      sync_ldap_users_and_groups(connection, is_ensuring_home_directory, request.fs,
+                                 failed_users=failed_ldap_users)
 
 
       request.audit = {
       request.audit = {
         'operation': 'SYNC_LDAP_USERS_GROUPS',
         'operation': 'SYNC_LDAP_USERS_GROUPS',
         'operationText': 'Successfully synced LDAP users/groups'
         'operationText': 'Successfully synced LDAP users/groups'
       }
       }
+
+      if failed_ldap_users:
+        unique_users = set(failed_ldap_users)
+        request.warn(_('Failed to import following users: %s') % ', '.join(unique_users))
+
       return redirect(reverse(list_users))
       return redirect(reverse(list_users))
   else:
   else:
     form = SyncLdapUsersGroupsForm()
     form = SyncLdapUsersGroupsForm()
 
 
   return render("sync_ldap_users_groups.mako", request, dict(path=request.path, form=form))
   return render("sync_ldap_users_groups.mako", request, dict(path=request.path, form=form))
 
 
-def sync_ldap_users_and_groups(connection, is_ensuring_home_directory=False, fs=None):
+
+def sync_ldap_users_and_groups(connection, is_ensuring_home_directory=False, fs=None, failed_users=None):
   try:
   try:
-    users = sync_ldap_users(connection)
-    groups = sync_ldap_groups(connection)
-  except ldap.LDAPError, e:
+    users = sync_ldap_users(connection, failed_users=failed_users)
+    groups = sync_ldap_groups(connection, failed_users=failed_users)
+  except (ldap.LDAPError, LdapBindException), e:
     LOG.error("LDAP Exception: %s" % e)
     LOG.error("LDAP Exception: %s" % e)
     raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
     raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
 
 
@@ -577,15 +598,18 @@ def sync_ldap_users_and_groups(connection, is_ensuring_home_directory=False, fs=
         raise PopupException(_("The import may not be complete, sync again."), detail=e)
         raise PopupException(_("The import may not be complete, sync again."), detail=e)
 
 
 
 
-def import_ldap_users(connection, user_pattern, sync_groups, import_by_dn, server=None):
-  return _import_ldap_users(connection, user_pattern, sync_groups=sync_groups, import_by_dn=import_by_dn, server=server)
+def import_ldap_users(connection, user_pattern, sync_groups, import_by_dn, server=None, failed_users=None):
+  return _import_ldap_users(connection, user_pattern, sync_groups=sync_groups, import_by_dn=import_by_dn, server=server,
+                            failed_users=failed_users)
 
 
 
 
-def import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users, import_by_dn):
-  return _import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users, import_by_dn)
+def import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users, import_by_dn,
+                       failed_users=None):
+  return _import_ldap_groups(connection, group_pattern, import_members, import_members_recursive, sync_users,
+                             import_by_dn, failed_users=failed_users)
 
 
 
 
-def sync_ldap_users(connection):
+def sync_ldap_users(connection, failed_users=None):
   """
   """
   Syncs LDAP user information. This will not import new
   Syncs LDAP user information. This will not import new
   users from LDAP. It is also not possible to import both a user and a
   users from LDAP. It is also not possible to import both a user and a
@@ -594,11 +618,11 @@ def sync_ldap_users(connection):
   """
   """
   users = User.objects.filter(userprofile__creation_method=str(UserProfile.CreationMethod.EXTERNAL)).all()
   users = User.objects.filter(userprofile__creation_method=str(UserProfile.CreationMethod.EXTERNAL)).all()
   for user in users:
   for user in users:
-    _import_ldap_users(connection, user.username)
+    _import_ldap_users(connection, user.username, failed_users=failed_users)
   return users
   return users
 
 
 
 
-def sync_ldap_groups(connection):
+def sync_ldap_groups(connection, failed_users=None):
   """
   """
   Syncs LDAP group memberships. This will not import new
   Syncs LDAP group memberships. This will not import new
   groups from LDAP. It is also not possible to import both a user and a
   groups from LDAP. It is also not possible to import both a user and a
@@ -607,7 +631,7 @@ def sync_ldap_groups(connection):
   """
   """
   groups = Group.objects.filter(group__in=LdapGroup.objects.all())
   groups = Group.objects.filter(group__in=LdapGroup.objects.all())
   for group in groups:
   for group in groups:
-    _import_ldap_groups(connection, group.name)
+    _import_ldap_groups(connection, group.name, failed_users=failed_users)
   return groups
   return groups
 
 
 
 
@@ -697,7 +721,7 @@ def _check_remove_last_super(user_obj):
     raise PopupException(_("You cannot remove the last active superuser from the configuration."), error_code=401)
     raise PopupException(_("You cannot remove the last active superuser from the configuration."), error_code=401)
 
 
 
 
-def _import_ldap_users(connection, username_pattern, sync_groups=False, import_by_dn=False, server=None):
+def _import_ldap_users(connection, username_pattern, sync_groups=False, import_by_dn=False, server=None, failed_users=None):
   """
   """
   Import a user from LDAP. If import_by_dn is true, this will import the user by
   Import a user from LDAP. If import_by_dn is true, this will import the user by
   the distinguished name, rather than the configured username attribute.
   the distinguished name, rather than the configured username attribute.
@@ -712,10 +736,10 @@ def _import_ldap_users(connection, username_pattern, sync_groups=False, import_b
     LOG.warn("Could not get LDAP details for users with pattern %s" % username_pattern)
     LOG.warn("Could not get LDAP details for users with pattern %s" % username_pattern)
     return None
     return None
 
 
-  return _import_ldap_users_info(connection, user_info, sync_groups, import_by_dn, server)
+  return _import_ldap_users_info(connection, user_info, sync_groups, import_by_dn, server, failed_users=failed_users)
 
 
 
 
-def _import_ldap_users_info(connection, user_info, sync_groups=False, import_by_dn=False, server=None):
+def _import_ldap_users_info(connection, user_info, sync_groups=False, import_by_dn=False, server=None, failed_users=None):
   """
   """
   Import user_info found through ldap_access.find_users.
   Import user_info found through ldap_access.find_users.
   """
   """
@@ -777,7 +801,8 @@ def _import_ldap_users_info(connection, user_info, sync_groups=False, import_by_
           # Add only if user isn't part of group.
           # Add only if user isn't part of group.
             current_ldap_groups.add(Group.objects.get(name=group_info['name']))
             current_ldap_groups.add(Group.objects.get(name=group_info['name']))
             if not user.groups.filter(name=group_info['name']).exists():
             if not user.groups.filter(name=group_info['name']).exists():
-              groups = import_ldap_groups(connection, group_info['dn'], import_members=False, import_members_recursive=False, sync_users=True, import_by_dn=True)
+              groups = import_ldap_groups(connection, group_info['dn'], import_members=False, import_members_recursive=False,
+                                          sync_users=True, import_by_dn=True, failed_users=failed_users)
               if groups:
               if groups:
                 new_groups.update(groups)
                 new_groups.update(groups)
         # Remove out of date groups
         # Remove out of date groups
@@ -789,12 +814,15 @@ def _import_ldap_users_info(connection, user_info, sync_groups=False, import_by_
         user.groups.add(*new_groups)
         user.groups.add(*new_groups)
         Group.objects.filter(group__in=remove_groups_filtered).delete()
         Group.objects.filter(group__in=remove_groups_filtered).delete()
     except (ValidationError, LdapSearchException) as e:
     except (ValidationError, LdapSearchException) as e:
+      if failed_users is None:
+        failed_users = []
+      failed_users.append(ldap_info['username'])
       LOG.warn('Could not import %s: %s' % (ldap_info['username'], e.message))
       LOG.warn('Could not import %s: %s' % (ldap_info['username'], e.message))
 
 
   return imported_users
   return imported_users
 
 
 
 
-def _import_ldap_members(connection, group, ldap_info, count=0, max_count=1):
+def _import_ldap_members(connection, group, ldap_info, count=0, max_count=1, failed_users=None):
   if count >= max_count:
   if count >= max_count:
     return None
     return None
 
 
@@ -815,19 +843,19 @@ def _import_ldap_members(connection, group, ldap_info, count=0, max_count=1):
 
 
   for user_info in users_info:
   for user_info in users_info:
     LOG.debug("Importing user %s into group %s" % (smart_str(user_info['dn']), smart_str(group.name)))
     LOG.debug("Importing user %s into group %s" % (smart_str(user_info['dn']), smart_str(group.name)))
-    users = _import_ldap_users(connection, smart_str(user_info['dn']), import_by_dn=True)
+    users = _import_ldap_users(connection, smart_str(user_info['dn']), import_by_dn=True, failed_users=None)
     group.user_set.add(*users)
     group.user_set.add(*users)
 
 
   for group_info in groups_info:
   for group_info in groups_info:
     LOG.debug("Importing group %s" % smart_str(group_info['dn']))
     LOG.debug("Importing group %s" % smart_str(group_info['dn']))
-    groups = _import_ldap_groups(connection, smart_str(group_info['dn']), import_by_dn=True)
+    groups = _import_ldap_groups(connection, smart_str(group_info['dn']), import_by_dn=True, failed_users=None)
 
 
     # Must find all members of subgroups
     # Must find all members of subgroups
     if len(groups) > 1:
     if len(groups) > 1:
       LOG.warn('Found multiple groups for member %s.' % smart_str(group_info['dn']))
       LOG.warn('Found multiple groups for member %s.' % smart_str(group_info['dn']))
     else:
     else:
       for group in groups:
       for group in groups:
-        _import_ldap_members(connection, group, group_info, count+1, max_count)
+        _import_ldap_members(connection, group, group_info, count+1, max_count, failed_users=failed_users)
 
 
   for posix_member in posix_members:
   for posix_member in posix_members:
     LOG.debug("Importing posix user %s into group %s" % (smart_str(posix_member), smart_str(group.name)))
     LOG.debug("Importing posix user %s into group %s" % (smart_str(posix_member), smart_str(group.name)))
@@ -838,13 +866,13 @@ def _import_ldap_members(connection, group, ldap_info, count=0, max_count=1):
       LOG.warn("Failed to find LDAP users: %s" % e)
       LOG.warn("Failed to find LDAP users: %s" % e)
 
 
     if user_info:
     if user_info:
-      users = _import_ldap_users_info(connection, user_info)
+      users = _import_ldap_users_info(connection, user_info, failed_users=failed_users)
       if users:
       if users:
         LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
         LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
         group.user_set.add(*users)
         group.user_set.add(*users)
 
 
 
 
-def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1):
+def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1, failed_users=None):
   if count >= max_count:
   if count >= max_count:
     return None
     return None
 
 
@@ -876,7 +904,7 @@ def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1):
 
 
     try:
     try:
       group = Group.objects.get(name=group_info['name'])
       group = Group.objects.get(name=group_info['name'])
-      _sync_ldap_members(connection, group, group_info, count+1, max_count)
+      _sync_ldap_members(connection, group, group_info, count+1, max_count, failed_users=failed_users)
     except Group.DoesNotExist:
     except Group.DoesNotExist:
       LOG.warn("Synchronizing group %s failed. Group does not exist." % smart_str(group.name))
       LOG.warn("Synchronizing group %s failed. Group does not exist." % smart_str(group.name))
 
 
@@ -897,7 +925,7 @@ def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1):
 
 
 
 
 def _import_ldap_nested_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
 def _import_ldap_nested_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
-                               sync_users=True, import_by_dn=False):
+                               sync_users=True, import_by_dn=False, failed_users=None):
   """
   """
   Import a group from LDAP. If import_members is true, this will also import any
   Import a group from LDAP. If import_members is true, this will also import any
   LDAP users that exist within the group. This will use nested groups logic.
   LDAP users that exist within the group. This will use nested groups logic.
@@ -939,11 +967,11 @@ def _import_ldap_nested_groups(connection, groupname_pattern, import_members=Fal
     max_count = recursive_import_members and desktop.conf.LDAP.NESTED_MEMBERS_SEARCH_DEPTH.get() or 1
     max_count = recursive_import_members and desktop.conf.LDAP.NESTED_MEMBERS_SEARCH_DEPTH.get() or 1
 
 
     if import_members:
     if import_members:
-      _import_ldap_members(connection, group, ldap_info, max_count=max_count)
+      _import_ldap_members(connection, group, ldap_info, max_count=max_count, failed_users=failed_users)
 
 
     # Sync users
     # Sync users
     if sync_users:
     if sync_users:
-      _sync_ldap_members(connection, group, ldap_info, max_count=max_count)
+      _sync_ldap_members(connection, group, ldap_info, max_count=max_count, failed_users=failed_users)
 
 
     group.save()
     group.save()
     groups.append(group)
     groups.append(group)
@@ -952,7 +980,7 @@ def _import_ldap_nested_groups(connection, groupname_pattern, import_members=Fal
 
 
 
 
 def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
 def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
-                                     sync_users=True, import_by_dn=False):
+                                     sync_users=True, import_by_dn=False, failed_users=None):
   """
   """
   Import a group from LDAP. If import_members is true, this will also import any
   Import a group from LDAP. If import_members is true, this will also import any
   LDAP users that exist within the group. This will use suboordinate group logic.
   LDAP users that exist within the group. This will use suboordinate group logic.
@@ -1008,7 +1036,7 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
 
 
       for member in members:
       for member in members:
         LOG.debug("Importing user %s" % smart_str(member))
         LOG.debug("Importing user %s" % smart_str(member))
-        group.user_set.add( *( _import_ldap_users(connection, member, import_by_dn=True) or [] ) )
+        group.user_set.add( *( _import_ldap_users(connection, member, import_by_dn=True, failed_users=failed_users) or [] ) )
 
 
     # Sync users
     # Sync users
     if sync_users:
     if sync_users:
@@ -1028,6 +1056,9 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
               user = ldap_access.get_ldap_user(username=ldap_info['username'])
               user = ldap_access.get_ldap_user(username=ldap_info['username'])
               group.user_set.add(user)
               group.user_set.add(user)
             except ValidationError, e:
             except ValidationError, e:
+              if failed_users is None:
+                failed_users = []
+              failed_users.append(ldap_info['username'])
               LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
               LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
             except User.DoesNotExist:
             except User.DoesNotExist:
               pass
               pass
@@ -1047,7 +1078,7 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
             LOG.warn("Failed to find LDAP user: %s" % e)
             LOG.warn("Failed to find LDAP user: %s" % e)
 
 
           if user_info:
           if user_info:
-            users = _import_ldap_users_info(connection, user_info, import_by_dn=False)
+            users = _import_ldap_users_info(connection, user_info, import_by_dn=False, failed_users=failed_users)
             if users:
             if users:
               LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
               LOG.debug("Adding member %s represented as users (should be a single user) %s to group %s" % (str(posix_member), str(users), str(group.name)))
               group.user_set.add(*users)
               group.user_set.add(*users)
@@ -1069,6 +1100,9 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
                 user = ldap_access.get_ldap_user(username=ldap_info['username'])
                 user = ldap_access.get_ldap_user(username=ldap_info['username'])
                 group.user_set.add(user)
                 group.user_set.add(user)
               except ValidationError, e:
               except ValidationError, e:
+                if failed_users is None:
+                  failed_users = []
+                failed_users.append(ldap_info['username'])
                 LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
                 LOG.warn('Could not sync %s: %s' % (ldap_info['username'], e.message))
               except User.DoesNotExist:
               except User.DoesNotExist:
                 pass
                 pass
@@ -1080,7 +1114,7 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
 
 
 
 
 def _import_ldap_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
 def _import_ldap_groups(connection, groupname_pattern, import_members=False, recursive_import_members=False,
-                        sync_users=True, import_by_dn=False):
+                        sync_users=True, import_by_dn=False, failed_users=None):
   """
   """
   Import a group from LDAP. If import_members is true, this will also import any
   Import a group from LDAP. If import_members is true, this will also import any
   LDAP users that exist within the group.
   LDAP users that exist within the group.
@@ -1091,14 +1125,16 @@ def _import_ldap_groups(connection, groupname_pattern, import_members=False, rec
                                             import_members=import_members,
                                             import_members=import_members,
                                             recursive_import_members=recursive_import_members,
                                             recursive_import_members=recursive_import_members,
                                             sync_users=sync_users,
                                             sync_users=sync_users,
-                                            import_by_dn=import_by_dn)
+                                            import_by_dn=import_by_dn,
+                                            failed_users=failed_users)
   else:
   else:
     return _import_ldap_nested_groups(connection=connection,
     return _import_ldap_nested_groups(connection=connection,
                                       groupname_pattern=groupname_pattern,
                                       groupname_pattern=groupname_pattern,
                                       import_members=import_members,
                                       import_members=import_members,
                                       recursive_import_members=recursive_import_members,
                                       recursive_import_members=recursive_import_members,
                                       sync_users=sync_users,
                                       sync_users=sync_users,
-                                      import_by_dn=import_by_dn)
+                                      import_by_dn=import_by_dn,
+                                      failed_users=failed_users)
 
 
 
 
 def _get_failed_operation_text(username, operation):
 def _get_failed_operation_text(username, operation):