فهرست منبع

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('/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
       response = c.post(URL, dict(server='nonsense', groupname_pattern='Test Administrators'))
       assert_true('Location' in response, response)

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

@@ -24,7 +24,7 @@ import json
 
 import ldap
 import ldap_access
-from ldap_access import LdapSearchException
+from ldap_access import LdapBindException, LdapSearchException
 
 from django.contrib.auth.models import User, Group
 from django.core.urlresolvers import reverse
@@ -429,9 +429,10 @@ def add_ldap_users(request):
       import_by_dn = form.cleaned_data['dn']
       server = form.cleaned_data.get('server')
       try:
+        failed_ldap_users = []
         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)
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
       except ValidationError, e:
@@ -452,6 +453,11 @@ def add_ldap_users(request):
           'operation': 'ADD_LDAP_USERS',
           '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))
   else:
     form = AddLdapUsersForm()
@@ -488,11 +494,12 @@ def add_ldap_groups(request):
       server = form.cleaned_data.get('server')
 
       try:
+        failed_ldap_users = []
         connection = ldap_access.get_connection_from_server(server)
         groups = import_ldap_groups(connection, groupname_pattern, import_members=import_members,
                                     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)
         raise PopupException(_('There was an error when communicating with LDAP'), detail=str(e))
       except ValidationError, e:
@@ -514,6 +521,11 @@ def add_ldap_groups(request):
           'operation': 'ADD_LDAP_GROUPS',
           '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))
       else:
         errors = form._errors.setdefault('groupname_pattern', ErrorList())
@@ -548,23 +560,32 @@ def sync_ldap_users_groups(request):
       server = form.cleaned_data.get('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 = {
         'operation': 'SYNC_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))
   else:
     form = SyncLdapUsersGroupsForm()
 
   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:
-    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)
     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)
 
 
-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
   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()
   for user in users:
-    _import_ldap_users(connection, user.username)
+    _import_ldap_users(connection, user.username, failed_users=failed_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
   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())
   for group in groups:
-    _import_ldap_groups(connection, group.name)
+    _import_ldap_groups(connection, group.name, failed_users=failed_users)
   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)
 
 
-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
   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)
     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.
   """
@@ -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.
             current_ldap_groups.add(Group.objects.get(name=group_info['name']))
             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:
                 new_groups.update(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)
         Group.objects.filter(group__in=remove_groups_filtered).delete()
     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))
 
   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:
     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:
     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)
 
   for group_info in groups_info:
     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
     if len(groups) > 1:
       LOG.warn('Found multiple groups for member %s.' % smart_str(group_info['dn']))
     else:
       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:
     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)
 
     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:
         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)
 
 
-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:
     return None
 
@@ -876,7 +904,7 @@ def _sync_ldap_members(connection, group, ldap_info, count=0, max_count=1):
 
     try:
       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:
       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,
-                               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
   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
 
     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
     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()
     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,
-                                     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
   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:
         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
     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'])
               group.user_set.add(user)
             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))
             except User.DoesNotExist:
               pass
@@ -1047,7 +1078,7 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
             LOG.warn("Failed to find LDAP user: %s" % e)
 
           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:
               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)
@@ -1069,6 +1100,9 @@ def _import_ldap_suboordinate_groups(connection, groupname_pattern, import_membe
                 user = ldap_access.get_ldap_user(username=ldap_info['username'])
                 group.user_set.add(user)
               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))
               except User.DoesNotExist:
                 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,
-                        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
   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,
                                             recursive_import_members=recursive_import_members,
                                             sync_users=sync_users,
-                                            import_by_dn=import_by_dn)
+                                            import_by_dn=import_by_dn,
+                                            failed_users=failed_users)
   else:
     return _import_ldap_nested_groups(connection=connection,
                                       groupname_pattern=groupname_pattern,
                                       import_members=import_members,
                                       recursive_import_members=recursive_import_members,
                                       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):