Fakultas Ilmu Komputer UI

Commit 27d48e51 authored by Alfina Megasiwi's avatar Alfina Megasiwi
Browse files

Auth Logging

parent d54422a3
......@@ -63,6 +63,7 @@ INSTALLED_APPS = [
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'django_grpc_framework',
'log',
]
MIDDLEWARE = [
......@@ -75,6 +76,7 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'log.middleware.api_logger_middleware.APILoggerMiddleware',
]
ROOT_URLCONF = 'auth_remindme.urls'
......@@ -99,7 +101,6 @@ TEMPLATES = [
WSGI_APPLICATION = 'auth_remindme.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
......@@ -217,4 +218,5 @@ SIMPLE_JWT = {
}
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
\ No newline at end of file
CORS_ORIGIN_ALLOW_ALL = True
import os
from log.events import Events
if os.environ.get('RUN_MAIN', None) != 'true':
default_app_config = 'log.apps.LoggerConfig'
API_LOGGER_SIGNAL = Events()
from django.apps import AppConfig
class LoggerConfig(AppConfig):
name = 'log'
verbose_name = 'DRF API Logger'
class EventsException(Exception):
pass
class Events:
def __init__(self, events=None):
if events is not None:
try:
iter(events)
except:
raise AttributeError("type object %s is not iterable" %
(type(events)))
else:
self.__events__ = events
def __getattr__(self, name):
if name.startswith('__'):
raise AttributeError("type object '%s' has no attribute '%s'" %
(self.__class__.__name__, name))
if hasattr(self, '__events__'):
if name not in self.__events__:
raise EventsException("Event '%s' is not declared" % name)
elif hasattr(self.__class__, '__events__'):
if name not in self.__class__.__events__:
raise EventsException("Event '%s' is not declared" % name)
self.__dict__[name] = ev = _EventSlot(name)
return ev
def __repr__(self):
return '<%s.%s object at %s>' % (self.__class__.__module__,
self.__class__.__name__,
hex(id(self)))
__str__ = __repr__
def __len__(self):
return len(self.__dict__.items())
def __iter__(self):
def gen(dictitems=self.__dict__.items()):
for attr, val in dictitems:
if isinstance(val, _EventSlot):
yield val
return gen()
class _EventSlot:
def __init__(self, name):
self.targets = []
self.__name__ = name
def __repr__(self):
return "event '%s'" % self.__name__
def __call__(self, *a, **kw):
for f in tuple(self.targets):
f(*a, **kw)
def __iadd__(self, f):
self.targets.append(f)
return self
def __isub__(self, f):
while f in self.targets:
self.targets.remove(f)
return self
def __len__(self):
return len(self.targets)
def __iter__(self):
def gen():
for target in self.targets:
yield target
return gen()
def __getitem__(self, key):
return self.targets[key]
import json
from django.urls import resolve
from datetime import datetime
from log import API_LOGGER_SIGNAL
from log.utils import get_headers, mask_sensitive_data
import jwt
from auth_remindme.settings import SIMPLE_JWT
class APILoggerMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
namespace = resolve(request.path).namespace
# Always skip Admin panel
if namespace == 'admin':
return self.get_response(request)
request_data = ''
try:
request_data = json.loads(request.body) if request.body else ''
except:
pass
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
headers = get_headers(request=request)
method = request.method
if response.get('content-type') in ('application/json', 'application/vnd.api+json',):
if getattr(response, 'streaming', False):
response_body = '** Streaming **'
else:
if type(response.content) == bytes:
response_body = json.loads(response.content.decode())
else:
response_body = json.loads(response.content)
api = request.get_full_path()
status_code=response.status_code
if status_code >= 500:
type_log = 2
elif status_code >=400 and status_code < 500:
type_log = 3
else:
type_log = 1
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
username = 'guest'
try:
valid_data = jwt.decode(token, SIMPLE_JWT['SIGNING_KEY'], algorithms=[SIMPLE_JWT['ALGORITHM']],)
username = valid_data['username']
except Exception as v:
print("validation error", v)
data = dict(
time=datetime.now(),
service="auth-remindme",
type=type_log,
host=mask_sensitive_data(headers).get("HOST"),
user=username,
method=method,
path=api,
code=status_code,
requestBody=str(mask_sensitive_data(request_data)),
response=str(mask_sensitive_data(response_body)),
)
API_LOGGER_SIGNAL.listen(**data)
else:
return response
return response
# Generated by Django 3.2.13 on 2022-05-27 03:43
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='APILogsModel',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('added_on', models.DateTimeField()),
('api', models.CharField(help_text='API URL', max_length=1024)),
('headers', models.TextField()),
('body', models.TextField()),
('method', models.CharField(db_index=True, max_length=10)),
('client_ip_address', models.CharField(max_length=50)),
('response', models.TextField()),
('status_code', models.PositiveSmallIntegerField(db_index=True, help_text='Response status code')),
('execution_time', models.DecimalField(decimal_places=5, help_text='Server execution time (Not complete response time.)', max_digits=8)),
],
options={
'verbose_name': 'API Log',
'verbose_name_plural': 'API Logs',
'db_table': 'drf_api_logs',
},
),
]
# Generated by Django 3.2.13 on 2022-05-27 03:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('log', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='APILogsModel',
),
]
import re
SENSITIVE_KEYS = ['password', 'token', 'access', 'refresh', 'AUTHORIZATION']
def get_headers(request=None):
regex = re.compile('^HTTP_')
return dict((regex.sub('', header), value) for (header, value)
in request.META.items() if header.startswith('HTTP_'))
def mask_sensitive_data(data):
if type(data) != dict:
return data
for key, value in data.items():
if key in SENSITIVE_KEYS:
data[key] = "***FILTERED***"
if type(value) == dict:
data[key] = mask_sensitive_data(data[key])
if type(value) == list:
data[key] = [mask_sensitive_data(item) for item in data[key]]
return data
......@@ -13,6 +13,9 @@ from djangorestframework_camel_case.render import (CamelCaseJSONRenderer,
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
from log import API_LOGGER_SIGNAL
import requests
class UserCreate(generics.CreateAPIView):
serializer_class = UserSerializer
permission_classes = [AllowAny]
......@@ -64,6 +67,15 @@ class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
token['username'] = user.username
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
\ No newline at end of file
serializer_class = MyTokenObtainPairSerializer
# LOGGING
def listener_log(**kwargs):
data_to_send = kwargs
url = "http://remindme-log.herokuapp.com/api/log/"
requests.post(url, data = data_to_send)
API_LOGGER_SIGNAL.listen += listener_log
\ No newline at end of file
......@@ -13,3 +13,4 @@ grpcio-tools>=1.5.2
psycopg2-binary>=2.8.0,<3.0.0
selenium>=3.141.0,<4.0.0
whitenoise>=5.2.0,<6.0.0
requests==2.27.1
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment