Skip to content

Commit

Permalink
Merge pull request #51 from KHIT93/v2-compliance-app
Browse files Browse the repository at this point in the history
New feature: Compliance
  • Loading branch information
KHIT93 committed Sep 9, 2020
2 parents 3210419 + ed799b0 commit 4ef7bdf
Show file tree
Hide file tree
Showing 32 changed files with 831 additions and 17 deletions.
4 changes: 2 additions & 2 deletions assets/src/js/pages/Admin/AuditLog/Detail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</label>
</div>
<div class="md:w-5/6">
{{ entry.module }}
{{ entry.content_type_name }}
</div>
</div>
<div class="md:flex md:items-center mb-6 mt-4">
Expand Down Expand Up @@ -103,7 +103,7 @@ export default {
methods: {
get() {
this.setLoading(true);
axios.get('/api/audit-log/'+this.id+'/').then(response => {
axios.get('/api/datalog/'+this.id+'/').then(response => {
this.entry = response.data;
this.setLoading(false);
}).catch(error => {
Expand Down
4 changes: 2 additions & 2 deletions assets/src/js/pages/Admin/AuditLog/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<tbody>
<tr v-for="entry in log" :key="entry.id" @click="detail(entry.id)">
<td>{{ entry.timestamp | ago }}</td>
<td>{{ entry.module }}</td>
<td>{{ entry.content_type_name }}</td>
<td>{{ entry.object_pk }}</td>
<td>{{ entry.action_name }}</td>
<td>{{ entry.actor_email }}</td>
Expand Down Expand Up @@ -56,7 +56,7 @@ export default {
if (query && page) {
qs = '?search='+query+'&page='+page;
}
axios.get('/api/audit-log/'+qs).then(response => {
axios.get('/api/datalog/'+qs).then(response => {
this.log = response.data.results;
this.setLoading(false);
}).catch(error => {
Expand Down
4 changes: 0 additions & 4 deletions assets/src/js/pages/Reports/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,6 @@
<div class="hover:bg-gray-200 text-sm">
<router-link to="/reports/sa-rule-hits">Spam rule hits</router-link>
</div> -->
<router-link v-if="user.is_staff" class="block border-t px-4 py-2 hover:bg-gray-200 no-underline text-gray-800" to="/admin/audit-log">
Audit Log<br/>
<span class=" text-xs text-gray-600">Show the audit log</span>
</router-link>
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions assets/src/js/pages/Tools/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
Application update status<br/>
<span class=" text-xs text-gray-600">Check for updates for the application itself</span>
</router-link>
<router-link class="block border-t px-4 py-2 hover:bg-gray-200 no-underline text-gray-800" to="/admin/audit-log">
Audit Log<br/>
<span class=" text-xs text-gray-600">Show the audit log</span>
</router-link>
<router-link class="block border-t border-b px-4 py-2 hover:bg-gray-200 no-underline text-gray-800" to="/tools/data-import">
Data import<br/>
<span class=" text-xs text-gray-600">Perform import of data into various parts of the application</span>
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ django-cryptography==1.0
django-encrypted-model-fields==0.5.8
django-extensions==3.0.8
django-filter==2.3.0
django-guardian==2.3.0
django-jsonfield==1.4.0
django-premailer==0.2.0
django-rest-auth==0.9.5
Expand Down
Empty file added src/compliance/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions src/compliance/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
5 changes: 5 additions & 0 deletions src/compliance/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ComplianceConfig(AppConfig):
name = 'compliance'
74 changes: 74 additions & 0 deletions src/compliance/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import threading
import time
from functools import partial
from django.apps import apps
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils.deprecation import MiddlewareMixin
from .models import DataLogEntry

threadlocal = threading.local()


class DataLogMiddleware(MiddlewareMixin):
"""
Middleware to couple the request's user to log items. This is accomplished by currying the signal receiver with the
user from the request (or None if the user is not authenticated).
"""

def process_request(self, request):
"""
Gets the current user from the request and prepares and connects a signal receiver with the user already
attached to it.
"""
# Initialize thread local storage
threadlocal.datalog = {
'signal_duid': (self.__class__, time.time()),
'remote_addr': request.META.get('REMOTE_ADDR'),
}

# In case of proxy, set 'original' address
if request.META.get('HTTP_X_FORWARDED_FOR'):
threadlocal.datalog['remote_addr'] = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]

# Connect signal for automatic logging
if hasattr(request, 'user') and getattr(request.user, 'is_authenticated', False):
set_actor = partial(self.set_actor, user=request.user, signal_duid=threadlocal.datalog['signal_duid'])
pre_save.connect(set_actor, sender=DataLogEntry, dispatch_uid=threadlocal.datalog['signal_duid'], weak=False)

def process_response(self, request, response):
"""
Disconnects the signal receiver to prevent it from staying active.
"""
if hasattr(threadlocal, 'datalog'):
pre_save.disconnect(sender=DataLogEntry, dispatch_uid=threadlocal.datalog['signal_duid'])

return response

def process_exception(self, request, exception):
"""
Disconnects the signal receiver to prevent it from staying active in case of an exception.
"""
if hasattr(threadlocal, 'datalog'):
pre_save.disconnect(sender=DataLogEntry, dispatch_uid=threadlocal.datalog['signal_duid'])

return None

@staticmethod
def set_actor(user, sender, instance, signal_duid, **kwargs):
"""
Signal receiver with an extra, required 'user' kwarg. This method becomes a real (valid) signal receiver when
it is curried with the actor.
"""
if hasattr(threadlocal, 'datalog'):
if signal_duid != threadlocal.datalog['signal_duid']:
return
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
auth_user_model = apps.get_model(app_label, model_name)
except ValueError:
auth_user_model = apps.get_model('auth', 'user')
if sender == DataLogEntry and isinstance(user, auth_user_model) and instance.actor is None:
instance.actor = user

instance.remote_addr = threadlocal.datalog['remote_addr']
40 changes: 40 additions & 0 deletions src/compliance/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.1.1 on 2020-09-07 19:16

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]

operations = [
migrations.CreateModel(
name='DataLogEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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.CharField(blank=True, choices=[('created', 'create'), ('updated', 'update'), ('deleted', 'delete')], max_length=32, null=True, verbose_name='action')),
('changes', models.TextField(blank=True, verbose_name='change message')),
('remote_addr', models.GenericIPAddressField(blank=True, null=True, verbose_name='remote address')),
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='timestamp')),
('additional_data', models.JSONField(blank=True, null=True, verbose_name='additional data')),
('actor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='actor')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype', verbose_name='content type')),
],
options={
'verbose_name': 'datalog entry',
'verbose_name_plural': 'datalog entries',
'ordering': ['-timestamp'],
'get_latest_by': 'timestamp',
},
),
]
Empty file.
Loading

0 comments on commit 4ef7bdf

Please sign in to comment.