瀏覽代碼

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: