Skip to content

Changes done in original Auditlog library for more effective use. #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/auditlog/admin.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
from django.contrib import admin
from .models import LogEntry
from .mixins import LogEntryAdminMixin
from .mixins import MiddlewareMixinclass
from .filters import ResourceTypeFilter
from .middleware import AuditlogMiddleware


class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
list_display = ['created', 'resource_url', 'action', 'msg_short', 'user_url']
search_fields = ['timestamp', 'object_repr', 'changes', 'actor__first_name', 'actor__last_name']
list_filter = ['action', ResourceTypeFilter]
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin,MiddlewareMixinclass):
list_display = ['created','user_url','action','entity_type','object_repr','msg_short','remote_addr']
search_fields = ['changes','remote_addr','object_repr']
list_filter = ['action',ResourceTypeFilter,'timestamp']
readonly_fields = ['created', 'resource_url', 'action', 'user_url', 'msg']
fieldsets = [
(None, {'fields': ['created', 'user_url', 'resource_url']}),
('Changes', {'fields': ['action', 'msg']}),
]

def has_add_permission(self, request): # remove "add" permission
return False

admin.site.register(LogEntry, LogEntryAdmin)
81 changes: 62 additions & 19 deletions src/auditlog/mixins.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import json

from django.utils.html import mark_safe
import datetime
from django.conf import settings
from .middleware import AuditlogMiddleware
import threading
import time


try:
from django.core import urlresolvers
except ImportError:
Expand All @@ -9,17 +15,36 @@
from django.urls.exceptions import NoReverseMatch
except ImportError:
from django.core.urlresolvers import NoReverseMatch
from django.utils.html import format_html
from django.utils.safestring import mark_safe
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object

MAX = 75

threadlocal = threading.local()

class LogEntryAdminMixin(object):
class MiddlewareMixinclass(MiddlewareMixin):
def disp_remote_addr(self,obj):
return obj.remote_addr
disp_remote_addr.short_description = "IP Address"

class LogEntryAdminMixin(object):
def created(self, obj):
return obj.timestamp.strftime('%Y-%m-%d %H:%M:%S')
created.short_description = 'Created'
return obj.timestamp.strftime('%b. %d, %Y, %I:%M %p') #Displays date in diff format
created.short_description = 'Date'

def entity_type(self,obj):
#entity type for KLC objects
if obj.content_type_id == 0:
return "N/A"
if obj.content_type_id == 8 or obj.content_type_id == 9: #Displays client for all models inside it
return "client"
if obj.content_type_id == 90 or obj.content_type_id == 10: #Displays user for all models inside it
return "user"

return obj.content_type.model
entity_type.short_description = "Entity type"

def user_url(self, obj):
if obj.actor:
Expand All @@ -28,10 +53,10 @@ def user_url(self, obj):
try:
link = urlresolvers.reverse(viewname, args=[obj.actor.id])
except NoReverseMatch:
return u'%s' % (obj.actor)
return format_html(u'<a href="{}">{}</a>', link, obj.actor)

return 'system'
return (obj.actor)
return ( obj.actor)
return (obj.object_repr) #Previously returned system ,now changed to return object_repr(username) to display username in last_login entries
user_url.allow_tags = True #Returns user whose last_login is changed which is the username itself.
user_url.short_description = 'User'

def resource_url(self, obj):
Expand All @@ -41,32 +66,50 @@ def resource_url(self, obj):
args = [obj.object_pk] if obj.object_id is None else [obj.object_id]
link = urlresolvers.reverse(viewname, args=args)
except NoReverseMatch:
obj_store = obj.object_repr
print('obj_store---- in alice test----',obj_store)
print("type(obj_store-----1st in alice test----",type(obj_store))
print(obj_store.isnumeric())
return obj.object_repr
else:
return format_html(u'<a href="{}">{}</a>', link, obj.object_repr)
resource_url.short_description = 'Resource'
obj_store = str(obj.object_repr)
return (obj.object_repr)
resource_url.allow_tags = True
resource_url.short_description = 'Entity name'

def msg_short(self, obj):
if obj.action == 2:
return '' # delete
return 'Deleted object' # delete
if obj.action == 3 or obj.action == 4 or obj.action == 1 and obj.content_type_id == 108 and obj.additional_data == "Client_name": #to display changes for actions of download-3,KLC-4
return obj.changes #and also when update action-1 is done for client name in Client Group
changes = json.loads(obj.changes)
s = '' if len(changes) == 1 else 's'
fields = ', '.join(changes.keys())
if len(fields) > MAX:
i = fields.rfind(' ', 0, MAX)
fields = fields[:i] + ' ..'
return '%d change%s: %s' % (len(changes), s, fields)
msg_short.short_description = 'Changes'
msg_short.short_description = 'Description'

def msg(self, obj):
if obj.action == 2:
return '' # delete
changes = json.loads(obj.changes)
msg = '<table><tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>'
msg = '<table><tr><th>No.</th><th>Field</th><th>From</th><th>To</th></tr>'
for i, field in enumerate(sorted(changes), 1):
value = [i, field] + (['***', '***'] if field == 'password' else changes[field])
msg += format_html('<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>', *value)

value = [i, field] + (['***', '***'] if field == 'password' or field == 'kaseya_password' or field == 'webroot_password' or field == 'db_password' or field == 'nlm_server_password' else changes[field]) #to display values of password fields as ****
#changes for last_login field in logging entries
if field == 'last_login':
for i in range(len(changes[field])): #iterating list
ologin_date = changes[field][0] #storing value at index 0 as old login date
ologin_date = datetime.datetime.strptime(ologin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
ologin_date = ologin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
nlogin_date = changes[field][1] #storing value at index 1 as new login date
nlogin_date = datetime.datetime.strptime(nlogin_date, '%Y-%m-%d %H:%M:%S.%f') #converting string into datetime obj
nlogin_date = nlogin_date.strftime("%m/%d/%Y %I:%M %p") #converting date into string type
value = [i,field] + [ologin_date,nlogin_date]
msg += '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' % tuple(value)
msg += '</table>'
return mark_safe(msg)
return mark_safe(msg) #mark_safe is used to return html code in Python
msg.allow_tags = True
msg.short_description = 'Changes'
42 changes: 16 additions & 26 deletions src/auditlog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist
from django.db import models, DEFAULT_DB_ALIAS
from django.db import models
from django.db.models import QuerySet, Q
from django.utils import formats, timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
Expand Down Expand Up @@ -164,18 +164,22 @@ class Action:
CREATE = 0
UPDATE = 1
DELETE = 2
DOWNLOAD = 3
KLC = 4

choices = (
(CREATE, _("create")),
(UPDATE, _("update")),
(DELETE, _("delete")),
(CREATE, _("Create")),
(UPDATE, _("Update")),
(DELETE, _("Delete")),
(DOWNLOAD,_("Download")),
(KLC,_("KLC"))
)

content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE, related_name='+', verbose_name=_("content type"))
content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE,related_name='+', verbose_name=_("content type"))
object_pk = models.CharField(db_index=True, max_length=255, verbose_name=_("object pk"))
object_id = models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name=_("object id"))
object_repr = models.TextField(verbose_name=_("object representation"))
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("action"))
object_repr = models.TextField(verbose_name=_("Entity name"))
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("Operation"))
changes = models.TextField(blank=True, verbose_name=_("change message"))
actor = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name='+', verbose_name=_("actor"))
remote_addr = models.GenericIPAddressField(blank=True, null=True, verbose_name=_("remote address"))
Expand All @@ -197,6 +201,10 @@ def __str__(self):
fstring = _("Updated {repr:s}")
elif self.action == self.Action.DELETE:
fstring = _("Deleted {repr:s}")
elif self.action == self.Action.DOWNLOAD:
fstring = _("Downloaded {repr:s}")
elif self.action == self.Action.KLC:
fstring = _("Kaseya Live Connect {repr:s}")
else:
fstring = _("Logged {repr:s}")

Expand Down Expand Up @@ -321,13 +329,9 @@ class AuditlogHistoryField(GenericRelation):

:param pk_indexable: Whether the primary key for this model is not an :py:class:`int` or :py:class:`long`.
:type pk_indexable: bool
:param delete_related: By default, including a generic relation into a model will cause all related objects to be
cascade-deleted when the parent object is deleted. Passing False to this overrides this behavior, retaining
the full auditlog history for the object. Defaults to True, because that's Django's default behavior.
:type delete_related: bool
"""

def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
def __init__(self, pk_indexable=True, **kwargs):
kwargs['to'] = LogEntry

if pk_indexable:
Expand All @@ -336,22 +340,8 @@ def __init__(self, pk_indexable=True, delete_related=True, **kwargs):
kwargs['object_id_field'] = 'object_pk'

kwargs['content_type_field'] = 'content_type'
self.delete_related = delete_related
super(AuditlogHistoryField, self).__init__(**kwargs)

def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
"""
Return all objects related to ``objs`` via this ``GenericRelation``.
"""
if self.delete_related:
return super(AuditlogHistoryField, self).bulk_related_objects(objs, using)

# When deleting, Collector.collect() finds related objects using this
# method. However, because we don't want to delete these related
# objects, we simply return an empty list.
return []


# South compatibility for AuditlogHistoryField
try:
from south.modelsinspector import add_introspection_rules
Expand Down