فهرست منبع

HUE-3056 [core] Refactor audit logging to enable each view action to specify log parameters

Also adds operationText for all logged operations
Jenny Kim 10 سال پیش
والد
کامیت
00005d6
3فایلهای تغییر یافته به همراه139 افزوده شده و 165 حذف شده
  1. 103 0
      apps/useradmin/src/useradmin/views.py
  2. 18 3
      desktop/core/src/desktop/auth/views.py
  3. 18 162
      desktop/core/src/desktop/middleware.py

+ 103 - 0
apps/useradmin/src/useradmin/views.py

@@ -129,6 +129,11 @@ def massage_groups_for_json(groups):
 
 def delete_user(request):
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'DELETE_USER',
+      'operationText': _get_failed_operation_text(request.user.username, 'DELETE_USER'),
+      'allowed': False
+    }
     raise PopupException(_("You must be a superuser to delete users."), error_code=401)
 
   if request.method != 'POST':
@@ -141,8 +146,14 @@ def delete_user(request):
     if str(request.user.id) in ids:
       raise PopupException(_("You cannot remove yourself."), error_code=401)
 
+    usernames = list(User.objects.filter(id__in=ids).values_list('username', flat=True))
     UserProfile.objects.filter(user__id__in=ids).delete()
     User.objects.filter(id__in=ids).delete()
+
+    request.audit = {
+      'operation': 'DELETE_USER',
+      'operationText': 'Deleted User(s): %s' % ', '.join(usernames)
+    }
   finally:
     __users_lock.release()
 
@@ -152,6 +163,11 @@ def delete_user(request):
 
 def delete_group(request):
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'DELETE_GROUP',
+       'operationText': _get_failed_operation_text(request.user.username, 'DELETE_GROUP'),
+      'allowed': False
+    }
     raise PopupException(_("You must be a superuser to delete groups."), error_code=401)
 
   if request.method == 'POST':
@@ -165,6 +181,10 @@ def delete_group(request):
       Group.objects.filter(name__in=group_names).delete()
 
       request.info(_('The groups were deleted.'))
+      request.audit = {
+        'operation': 'DELETE_GROUP',
+        'operationText': 'Deleted Group(s): %s' % ', '.join(group_names)
+      }
       return redirect(reverse(list_groups))
     except Group.DoesNotExist:
       raise PopupException(_("Group not found."), error_code=404)
@@ -189,6 +209,13 @@ def edit_user(request, username=None):
   @param username:      Default to None, when creating a new user
   """
   if request.user.username != username and not request.user.is_superuser:
+    request.audit = {'allowed': False}
+    if username is not None:
+      request.audit['operation'] = 'EDIT_USER'
+      request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'EDIT_USER')
+    else:
+      request.audit['operation'] = 'CREATE_USER'
+      request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'CREATE_USER')
     raise PopupException(_("You must be a superuser to add or edit another user."), error_code=401)
 
   userprofile = get_profile(request.user)
@@ -244,9 +271,22 @@ def edit_user(request, username=None):
         except (IOError, WebHdfsException), e:
           request.error(_('Cannot make home directory for user %s.') % instance.username)
 
+      # Audit log
+      if username is not None:
+        request.audit = {
+          'operation': 'EDIT_USER',
+          'operationText': 'Edited User with username: %s' % username
+        }
+      else:
+        request.audit = {
+          'operation': 'CREATE_USER',
+          'operationText': 'Created User with username: %s' % instance.username
+        }
+
       if require_change_password(userprofile):
         userprofile.first_login = False
         userprofile.save()
+
         if request.user.is_superuser:
           return redirect(reverse('about:index'))
         else:
@@ -282,6 +322,14 @@ def edit_group(request, name=None):
   Only superusers may create a group
   """
   if not request.user.is_superuser:
+    request.audit = {'allowed': False}
+    if name is not None:
+      request.audit['operation'] = 'EDIT_GROUP'
+      request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'EDIT_GROUP')
+    else:
+      request.audit['operation'] = 'CREATE_GROUP'
+      request.audit['operationText'] = _get_failed_operation_text(request.user.username, 'CREATE_GROUP')
+
     raise PopupException(_("You must be a superuser to add or edit a group."), error_code=401)
 
   if name is not None:
@@ -294,6 +342,21 @@ def edit_group(request, name=None):
     if form.is_valid():
       form.save()
       request.info(_('Group information updated'))
+
+      # Audit log
+      if name is not None:
+        usernames = instance.user_set.all().values_list('username', flat=True)
+        request.audit = {
+          'operation': 'EDIT_GROUP',
+          'operationText': 'Edited Group: %s, with member(s): %s' % (name, ', '.join(usernames))
+        }
+      else:
+        user_ids = request.POST.getlist('members', [])
+        usernames = User.objects.filter(pk__in=user_ids).values_list('username', flat=True)
+        request.audit = {
+          'operation': 'CREATE_GROUP',
+          'operationText': 'Created Group: %s, with member(s): %s' % (request.POST.get('name', ''), ', '.join(usernames))
+        }
       return list_groups(request)
 
   else:
@@ -316,6 +379,11 @@ def edit_permission(request, app=None, priv=None):
   Only superusers may modify permissions
   """
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'EDIT_PERMISSION',
+      'operationText': _get_failed_operation_text(request.user.username, 'EDIT_PERMISSION'),
+      'allowed': False
+    }
     raise PopupException(_("You must be a superuser to change permissions."), error_code=401)
 
   instance = HuePermission.objects.get(app=app, action=priv)
@@ -325,6 +393,10 @@ def edit_permission(request, app=None, priv=None):
     if form.is_valid():
       form.save()
       request.info(_('Permission information updated'))
+      request.audit = {
+        'operation': 'EDIT_PERMISSION',
+        'operationText': 'Successfully edited permissions: %(app)s/%(priv)s' % {'app': app, 'priv': priv}
+      }
       return render("list_permissions.mako", request, dict(permissions=HuePermission.objects.all()))
 
   else:
@@ -343,6 +415,11 @@ def add_ldap_users(request):
   If the LDAP request failed, the error message is generic right now.
   """
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'ADD_LDAP_USERS',
+      'operationText': _get_failed_operation_text(request.user.username, 'ADD_LDAP_USERS'),
+      'allowed': False,
+    }
     raise PopupException(_("You must be a superuser to add another user."), error_code=401)
 
   if request.method == 'POST':
@@ -371,6 +448,10 @@ def add_ldap_users(request):
         errors = form._errors.setdefault('username_pattern', ErrorList())
         errors.append(_('Could not get LDAP details for users in pattern %s.') % username_pattern)
       else:
+        request.audit = {
+          'operation': 'ADD_LDAP_USERS',
+          'operationText': 'Added/Synced LDAP username(s): %s' % username_pattern
+        }
         return redirect(reverse(list_users))
   else:
     form = AddLdapUsersForm()
@@ -389,6 +470,11 @@ def add_ldap_groups(request):
   all unimported users.
   """
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'ADD_LDAP_GROUPS',
+      'operationText': _get_failed_operation_text(request.user.username, 'ADD_LDAP_GROUPS'),
+      'allowed': False,
+    }
     raise PopupException(_("You must be a superuser to add another group."), error_code=401)
 
   if request.method == 'POST':
@@ -424,6 +510,10 @@ def add_ldap_groups(request):
             raise PopupException(_("Exception creating home directory for LDAP user %s in group %s.") % (user, group), detail=e)
 
       if groups:
+        request.audit = {
+          'operation': 'ADD_LDAP_GROUPS',
+          'operationText': 'Added LDAP Group(s): %s' % groupname_pattern
+        }
         return redirect(reverse(list_groups))
       else:
         errors = form._errors.setdefault('groupname_pattern', ErrorList())
@@ -444,6 +534,11 @@ def sync_ldap_users_groups(request):
   server's current state.
   """
   if not request.user.is_superuser:
+    request.audit = {
+      'operation': 'SYNC_LDAP_USERS_GROUPS',
+      'operationText': _get_failed_operation_text(request.user.username, 'SYNC_LDAP_USERS_GROUPS'),
+      'allowed': False
+    }
     raise PopupException(_("You must be a superuser to sync the LDAP users/groups."), error_code=401)
 
   if request.method == 'POST':
@@ -455,6 +550,10 @@ def sync_ldap_users_groups(request):
 
       sync_ldap_users_and_groups(connection, is_ensuring_home_directory, request.fs)
 
+      request.audit = {
+        'operation': 'SYNC_LDAP_USERS_GROUPS',
+        'operationText': 'Successfully synced LDAP users/groups'
+      }
       return redirect(reverse(list_users))
   else:
     form = SyncLdapUsersGroupsForm()
@@ -1000,3 +1099,7 @@ def _import_ldap_groups(connection, groupname_pattern, import_members=False, rec
                                       recursive_import_members=recursive_import_members,
                                       sync_users=sync_users,
                                       import_by_dn=import_by_dn)
+
+
+def _get_failed_operation_text(username, operation):
+    return '%(username)s is not allowed to perform %(operation)s' % {'username': username, 'operation': operation}

+ 18 - 3
desktop/core/src/desktop/auth/views.py

@@ -97,6 +97,11 @@ def dt_login(request):
     AuthenticationForm = auth_forms.AuthenticationForm
 
   if request.method == 'POST':
+    request.audit = {
+      'operation': 'USER_LOGIN',
+      'username': request.POST.get('username')
+    }
+
     # For first login, need to validate user info!
     first_user_form = is_first_login_ever and UserCreationForm(data=request.POST) or None
     first_user = first_user_form and first_user_form.is_valid()
@@ -129,11 +134,15 @@ def dt_login(request):
         userprofile.first_login = False
         userprofile.save()
 
-        access_warn(request, '"%s" login ok' % (user.username,))
+        msg = 'Successful login for user: %s' % user.username
+        request.audit['operationText'] = msg
+        access_warn(request, msg)
         return HttpResponseRedirect(redirect_to)
-
       else:
-        access_warn(request, 'Failed login for user "%s"' % (request.POST.get('username'),))
+        request.audit['allowed'] = False
+        msg = 'Failed login for user: %s' % request.POST.get('username')
+        request.audit['operationText'] = msg
+        access_warn(request, msg)
 
   else:
     first_user_form = None
@@ -159,6 +168,12 @@ def dt_login(request):
 
 def dt_logout(request, next_page=None):
   """Log out the user"""
+  username = request.user.get_username()
+  request.audit = {
+    'username': username,
+    'operation': 'USER_LOGOUT',
+    'operationText': 'Logged out user: %s' % username
+  }
   backends = get_backends()
   if backends:
     for backend in backends:

+ 18 - 162
desktop/core/src/desktop/middleware.py

@@ -337,65 +337,6 @@ class JsonMessage(object):
 
 class AuditLoggingMiddleware(object):
 
-  username_re = get_username_re_rule()
-  groupname_re = get_groupname_re_rule()
-
-  operations = {
-    '/accounts/login': {
-      'operation': 'USER_LOGIN',
-      'success_status': 302
-    },
-    '/accounts/logout': {
-      'operation': 'USER_LOGOUT',
-      'success_status': 302
-    },
-    '/useradmin/users/add_ldap_users': {
-      'operation': 'ADD_LDAP_USERS',
-      'success_status': 302
-    },
-    '/useradmin/users/add_ldap_groups': {
-      'operation': 'ADD_LDAP_GROUPS',
-      'success_status': 302
-    },
-    '/useradmin/users/sync_ldap_users_groups': {
-      'operation': 'SYNC_LDAP_USERS_GROUPS',
-      'success_status': 302
-    },
-    '/useradmin/users/new': {
-      'operation': 'CREATE_USER',
-      'success_status': 302
-    },
-    '/useradmin/groups/new': {
-      'operation': 'CREATE_GROUP',
-      'success_status': 200
-    },
-    '/useradmin/users/delete': {
-      'operation': 'DELETE_USER',
-      'success_status': 302
-    },
-    '/useradmin/groups/delete': {
-      'operation': 'DELETE_GROUP',
-      'success_status': 302
-    }
-  }
-
-  operation_patterns = {
-    '/useradmin/permissions/edit/(?P<app>.*)/(?P<priv>.*)': {
-      'operation': 'EDIT_PERMISSION',
-      'success_status': 200
-    },
-    '/useradmin/users/edit/(?P<username>%s)' % (username_re,): {
-      'operation': 'EDIT_USER',
-      'success_status': 302
-    },
-    '/useradmin/groups/edit/(?P<name>%s)' % (groupname_re,): {
-      'operation': 'EDIT_GROUP',
-      'success_status': 200
-    }
-  }
-
-  process_view_operations = ('USER_LOGOUT', 'DELETE_USER')
-
   def __init__(self):
     from desktop.conf import AUDIT_EVENT_LOG_DIR, SERVER_USER
 
@@ -405,138 +346,53 @@ class AuditLoggingMiddleware(object):
       LOG.info('Unloading AuditLoggingMiddleware')
       raise exceptions.MiddlewareNotUsed
 
-  def process_view(self, request, view_func, view_args, view_kwargs):
-    try:
-      operation = self._get_operation(request.path)
-      if self._is_valid_request(operation):
-        self._log_message(operation, request)
-    except Exception, e:
-      LOG.error('Could not audit the request: %s' % e)
-
-    return None
-
   def process_response(self, request, response):
     response['audited'] = False
     try:
-      operation = self._get_operation(request.path)
-      if self._is_valid_response(operation, request, response):
-        self._log_message(operation, request, response)
+      if hasattr(request, 'audit') and request.audit is not None:
+        self._log_message(request, response)
         response['audited'] = True
     except Exception, e:
       LOG.error('Could not audit the request: %s' % e)
     return response
 
-  def _is_valid_request(self, operation):
-    return operation and (operation in self.process_view_operations)
-
-  def _is_valid_response(self, operation, request, response):
-    success_status = self._get_success_status(request.path)
-    is_valid = False
-    if self._is_invalid_login(operation, request):
-      is_valid = True
-    elif request.method == 'POST' and \
-       operation and (operation not in self.process_view_operations) and \
-       (response.status_code == success_status or response.status_code == 401):  # also log unauthorized operations
-      is_valid = True
-    return is_valid
-
-  def _log_message(self, operation, request, response=None):
+  def _log_message(self, request, response=None):
     audit_logger = get_audit_logger()
 
     audit_logger.debug(JsonMessage(**{
-      'username': self._get_username(operation, request),
+      'username': self._get_username(request),
       'impersonator': self.impersonator,
       'ipAddress': self._get_client_ip(request),
-      'operation': operation,
-      'operationText': self._get_operation_text(operation, request),
+      'operation': request.audit['operation'],
+      'operationText': request.audit.get('operationText', ''),
       'eventTime': self._milliseconds_since_epoch(),
-      'allowed': self._get_allowed(operation, request, response),
+      'allowed': self._get_allowed(request, response),
       'service': get_app_name(request),
       'url': request.path
     }))
 
-  def _is_invalid_login(self, operation, request):
-    return request.method == 'POST' and operation == 'USER_LOGIN' and not request.user.is_authenticated()
-
   def _get_client_ip(self, request):
     x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
     if x_forwarded_for:
         x_forwarded_for = x_forwarded_for.split(',')[0]
     return request.META.get('HTTP_CLIENT_IP') or x_forwarded_for or request.META.get('REMOTE_ADDR')
 
-  def _get_username(self, operation, request):
-    # On invalid login attempt, get username from form
-    if self._is_invalid_login(operation, request):
-      return request.POST.get('username', 'anonymous')
+  def _get_username(self, request):
+    username = 'anonymous'
+    if request.audit.get('username', None):
+      username = request.audit.get('username')
     elif hasattr(request, 'user') and not request.user.is_anonymous():
-      return request.user.get_username()
-    else:
-      return 'anonymous'
+      username = request.user.get_username()
+    return username
 
   def _milliseconds_since_epoch(self):
     return int(time.time() * 1000)
 
-  def _get_allowed(self, operation, request, response=None):
-    # Set allowed to False on failed login attempts since returned status_code is actually 200
-    if self._is_invalid_login(operation, request):
-      return False
-    # No response on logout
-    elif response is not None:
-      return response.status_code != 401
-    else:
-      return True
-
-  def _get_operation_dict(self, path):
-    url = path.rstrip('/')
-
-    if url in AuditLoggingMiddleware.operations:
-      return AuditLoggingMiddleware.operations[url]
-    else:
-      for regex, operation_dict in AuditLoggingMiddleware.operation_patterns.items():
-        pattern = re.compile(regex)
-        if pattern.match(url):
-          return operation_dict
-    return None
-
-  def _get_operation(self, path):
-    operation_dict = self._get_operation_dict(path)
-    if operation_dict:
-      return operation_dict['operation']
-    else:
-      return None
-
-  def _get_success_status(self, path):
-    operation_dict = self._get_operation_dict(path)
-    if operation_dict:
-      return operation_dict['success_status']
-    else:
-      return None
-
-  def _get_operation_text(self, operation, request):
-    if request.method == 'POST':
-      if operation == 'CREATE_USER':
-        return 'Created User with username: %s' % request.POST.get('username', '')
-      elif operation == 'EDIT_USER':
-        return 'Edited User with username: %s' % request.POST.get('username', '')
-      elif operation == 'DELETE_USER':
-        usernames = self._get_usernames(request.POST.getlist('user_ids', []))
-        return 'Deleted User(s): %s' % ', '.join(usernames)
-      elif operation == 'ADD_LDAP_USERS':
-        return 'Added/Synced LDAP username(s): %s' % ', '.join(request.POST.getlist('username_pattern', []))
-      elif operation == 'CREATE_GROUP':
-        usernames = self._get_usernames(request.POST.getlist('members', []))
-        return 'Created Group: %s, with member(s): %s' % (request.POST.get('name', ''), ', '.join(usernames))
-      elif operation == 'EDIT_GROUP':
-        usernames = self._get_usernames(request.POST.getlist('members', []))
-        return 'Edited Group: %s, with member(s): %s' % (request.POST.get('name', ''), ', '.join(usernames))
-      elif operation == 'DELETE_GROUP':
-        return 'Deleted Group(s): %s' % ', '.join(request.POST.getlist('group_names', []))
-      elif operation == 'ADD_LDAP_GROUPS':
-        return 'Added LDAP Group(s): %s' % ', '.join(request.POST.getlist('groupname_pattern', []))
-    return ''
-
-  def _get_usernames(self, user_ids):
-    return User.objects.filter(pk__in=user_ids).values_list('username', flat=True)
+  def _get_allowed(self, request, response=None):
+    allowed = response.status_code != 401
+    if 'allowed' in request.audit:
+      return request.audit['allowed']
+    return allowed
 
 
 try: