diff --git a/CHANGELOG.md b/CHANGELOG.md index ada2d64..c6458ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,7 @@ -# 1.0.0 (2023-05-25) -To learn about the release highlights, please check out the [v1.0.0 release announcement]() \ No newline at end of file +## 1.1.x +### 1.1.0 +#### Features +* Implemented REST APIs for charts data in order to support the new interface integration + +# 1.0.0 (2023-06-16) +To learn about the release highlights, please check out the [v1.0.0 release announcement]() diff --git a/README.md b/README.md index b881006..fe05885 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ In detail, it sends several types of alerts: This alert is dispatched if the system is logged by a user from a country where they have never authenticated before. -*For futher details: [Wiki - About](https://github.com/certego/BuffaLogs/wiki/1.-About)* +*For further details: [Wiki - About](https://github.com/certego/BuffaLogs/wiki/1.-About)* ## BuffaLogs is participating in GSoC 2023 thanks to Honeynet project and IntelOwl! @@ -83,7 +83,7 @@ After that, there are two ways of running BuffaLogs, depending on your system co ![buffalogs_dashboard_screenshot](https://user-images.githubusercontent.com/33703137/220879987-b6453e9d-0129-45c1-bc26-0542005e8730.png) -*For futher examples: [Wiki - Example](https://github.com/certego/BuffaLogs/wiki/2.-Example)* +*For further examples: [Wiki - Example](https://github.com/certego/BuffaLogs/wiki/2.-Example)* ## Logs Structure @@ -112,7 +112,13 @@ For a basic analysis to detect only impossible travel logins, the *user_agent* f ## BuffaLogs Architecture ![Buffalogs_Architecture](https://user-images.githubusercontent.com/33703137/220896332-4fe08f32-1879-4150-bd5d-9df9dc21a7a7.jpg) -*For futher details: [Wiki - Architecture](https://github.com/certego/BuffaLogs/wiki/3.-Architecture)* +*For further details: [Wiki - Architecture](https://github.com/certego/BuffaLogs/wiki/3.-Architecture)* + +## REST APIs + +In order to obtain the charts data, you can query the REST APIs. + +*For further details: [Wiki - REST APIs](https://github.com/certego/BuffaLogs/wiki/5.-REST-APIs)* ## Uninstall diff --git a/buffalogs/buffalogs/urls.py b/buffalogs/buffalogs/urls.py index e3ca249..11e504f 100644 --- a/buffalogs/buffalogs/urls.py +++ b/buffalogs/buffalogs/urls.py @@ -31,5 +31,8 @@ path("users//all_logins", views.all_logins, name="all_logins"), path("users//alerts/get_alerts", views.get_alerts, name="get_alerts"), path("users//alerts", views.alerts, name="alerts"), + path("users_pie_chart_api/", views.users_pie_chart_api, name="users_pie_chart_api"), + path("alerts_line_chart_api/", views.alerts_line_chart_api, name="alerts_line_chart_api"), + path("world_map_chart_api/", views.world_map_chart_api, name="world_map_chart_api"), path("authentication/", include("authentication.urls")), ] diff --git a/buffalogs/impossible_travel/dashboard/charts.py b/buffalogs/impossible_travel/dashboard/charts.py index 1e234bd..3b78388 100644 --- a/buffalogs/impossible_travel/dashboard/charts.py +++ b/buffalogs/impossible_travel/dashboard/charts.py @@ -1,3 +1,5 @@ +import json +import os from datetime import timedelta import pygal @@ -10,6 +12,13 @@ imp_travel = impossible_travel.Impossible_Travel() +def _load_data(name): + DATA_PATH = "impossible_travel/dashboard/" + with open(os.path.join(DATA_PATH, name + ".json")) as file: + data = json.load(file) + return data + + def users_pie_chart(start, end): custom_style = Style( background="transparent", @@ -121,192 +130,7 @@ def world_map_chart(start, end): height=130, show_legend=False, ) - countries = { - "ad": "Andorra", - "ae": "United Arab Emirates", - "af": "Afghanistan", - "al": "Albania", - "am": "Armenia", - "ao": "Angola", - "aq": "Antarctica", - "ar": "Argentina", - "at": "Austria", - "au": "Australia", - "az": "Azerbaijan", - "ba": "Bosnia and Herzegovina", - "bd": "Bangladesh", - "be": "Belgium", - "bf": "Burkina Faso", - "bg": "Bulgaria", - "bh": "Bahrain", - "bi": "Burundi", - "bj": "Benin", - "bn": "Brunei Darussalam", - "bo": "Bolivia, Plurinational State of", - "br": "Brazil", - "bt": "Bhutan", - "bw": "Botswana", - "by": "Belarus", - "bz": "Belize", - "ca": "Canada", - "cd": "Congo, the Democratic Republic of the", - "cf": "Central African Republic", - "cg": "Congo", - "ch": "Switzerland", - "ci": "Cote d'Ivoire", - "cl": "Chile", - "cm": "Cameroon", - "cn": "China", - "co": "Colombia", - "cr": "Costa Rica", - "cu": "Cuba", - "cv": "Cape Verde", - "cy": "Cyprus", - "cz": "Czech Republic", - "de": "Germany", - "dj": "Djibouti", - "dk": "Denmark", - "do": "Dominican Republic", - "dz": "Algeria", - "ec": "Ecuador", - "ee": "Estonia", - "eg": "Egypt", - "eh": "Western Sahara", - "er": "Eritrea", - "es": "Spain", - "et": "Ethiopia", - "fi": "Finland", - "fr": "France", - "ga": "Gabon", - "gb": "United Kingdom", - "ge": "Georgia", - "gf": "French Guiana", - "gh": "Ghana", - "gl": "Greenland", - "gm": "Gambia", - "gn": "Guinea", - "gq": "Equatorial Guinea", - "gr": "Greece", - "gt": "Guatemala", - "gu": "Guam", - "gw": "Guinea-Bissau", - "gy": "Guyana", - "hk": "Hong Kong", - "hn": "Honduras", - "hr": "Croatia", - "ht": "Haiti", - "hu": "Hungary", - "id": "Indonesia", - "ie": "Ireland", - "il": "Israel", - "in": "India", - "iq": "Iraq", - "ir": "Iran, Islamic Republic of", - "is": "Iceland", - "it": "Italy", - "jm": "Jamaica", - "jo": "Jordan", - "jp": "Japan", - "ke": "Kenya", - "kg": "Kyrgyzstan", - "kh": "Cambodia", - "kp": "Korea, Democratic People's Republic of", - "kr": "Korea, Republic of", - "kw": "Kuwait", - "kz": "Kazakhstan", - "la": "Lao People's Democratic Republic", - "lb": "Lebanon", - "li": "Liechtenstein", - "lk": "Sri Lanka", - "lr": "Liberia", - "ls": "Lesotho", - "lt": "Lithuania", - "lu": "Luxembourg", - "lv": "Latvia", - "ly": "Libyan Arab Jamahiriya", - "ma": "Morocco", - "mc": "Monaco", - "md": "Moldova, Republic of", - "me": "Montenegro", - "mg": "Madagascar", - "mk": "Macedonia, the former Yugoslav Republic of", - "ml": "Mali", - "mm": "Myanmar", - "mn": "Mongolia", - "mo": "Macao", - "mr": "Mauritania", - "mt": "Malta", - "mu": "Mauritius", - "mv": "Maldives", - "mw": "Malawi", - "mx": "Mexico", - "my": "Malaysia", - "mz": "Mozambique", - "na": "Namibia", - "ne": "Niger", - "ng": "Nigeria", - "ni": "Nicaragua", - "nl": "Netherlands", - "no": "Norway", - "np": "Nepal", - "nz": "New Zealand", - "om": "Oman", - "pa": "Panama", - "pe": "Peru", - "pg": "Papua New Guinea", - "ph": "Philippines", - "pk": "Pakistan", - "pl": "Poland", - "pr": "Puerto Rico", - "ps": "Palestine, State of", - "pt": "Portugal", - "py": "Paraguay", - "re": "Reunion", - "ro": "Romania", - "rs": "Serbia", - "ru": "Russian Federation", - "rw": "Rwanda", - "sa": "Saudi Arabia", - "sc": "Seychelles", - "sd": "Sudan", - "se": "Sweden", - "sg": "Singapore", - "sh": "Saint Helena, Ascension and Tristan da Cunha", - "si": "Slovenia", - "sk": "Slovakia", - "sl": "Sierra Leone", - "sm": "San Marino", - "sn": "Senegal", - "so": "Somalia", - "sr": "Suriname", - "st": "Sao Tome and Principe", - "sv": "El Salvador", - "sy": "Syrian Arab Republic", - "sz": "Swaziland", - "td": "Chad", - "tg": "Togo", - "th": "Thailand", - "tj": "Tajikistan", - "tl": "Timor-Leste", - "tm": "Turkmenistan", - "tn": "Tunisia", - "tr": "Turkey", - "tw": "Taiwan (Republic of China)", - "tz": "Tanzania, United Republic of", - "ua": "Ukraine", - "ug": "Uganda", - "us": "United States", - "uy": "Uruguay", - "uz": "Uzbekistan", - "va": "Holy See (Vatican City State)", - "ve": "Venezuela, Bolivarian Republic of", - "vn": "Viet Nam", - "ye": "Yemen", - "yt": "Mayotte", - "za": "South Africa", - "zm": "Zambia", - "zw": "Zimbabwe", - } + countries = _load_data("countries") tmp = {} for key, value in countries.items(): country_alerts = Alert.objects.filter( diff --git a/buffalogs/impossible_travel/dashboard/countries.json b/buffalogs/impossible_travel/dashboard/countries.json new file mode 100644 index 0000000..6ee3125 --- /dev/null +++ b/buffalogs/impossible_travel/dashboard/countries.json @@ -0,0 +1,186 @@ +{ + "ad": "Andorra", + "ae": "United Arab Emirates", + "af": "Afghanistan", + "al": "Albania", + "am": "Armenia", + "ao": "Angola", + "aq": "Antarctica", + "ar": "Argentina", + "at": "Austria", + "au": "Australia", + "az": "Azerbaijan", + "ba": "Bosnia and Herzegovina", + "bd": "Bangladesh", + "be": "Belgium", + "bf": "Burkina Faso", + "bg": "Bulgaria", + "bh": "Bahrain", + "bi": "Burundi", + "bj": "Benin", + "bn": "Brunei Darussalam", + "bo": "Bolivia, Plurinational State of", + "br": "Brazil", + "bt": "Bhutan", + "bw": "Botswana", + "by": "Belarus", + "bz": "Belize", + "ca": "Canada", + "cd": "Congo, the Democratic Republic of the", + "cf": "Central African Republic", + "cg": "Congo", + "ch": "Switzerland", + "ci": "Cote d'Ivoire", + "cl": "Chile", + "cm": "Cameroon", + "cn": "China", + "co": "Colombia", + "cr": "Costa Rica", + "cu": "Cuba", + "cv": "Cape Verde", + "cy": "Cyprus", + "cz": "Czech Republic", + "de": "Germany", + "dj": "Djibouti", + "dk": "Denmark", + "do": "Dominican Republic", + "dz": "Algeria", + "ec": "Ecuador", + "ee": "Estonia", + "eg": "Egypt", + "eh": "Western Sahara", + "er": "Eritrea", + "es": "Spain", + "et": "Ethiopia", + "fi": "Finland", + "fr": "France", + "ga": "Gabon", + "gb": "United Kingdom", + "ge": "Georgia", + "gf": "French Guiana", + "gh": "Ghana", + "gl": "Greenland", + "gm": "Gambia", + "gn": "Guinea", + "gq": "Equatorial Guinea", + "gr": "Greece", + "gt": "Guatemala", + "gu": "Guam", + "gw": "Guinea-Bissau", + "gy": "Guyana", + "hk": "Hong Kong", + "hn": "Honduras", + "hr": "Croatia", + "ht": "Haiti", + "hu": "Hungary", + "id": "Indonesia", + "ie": "Ireland", + "il": "Israel", + "in": "India", + "iq": "Iraq", + "ir": "Iran, Islamic Republic of", + "is": "Iceland", + "it": "Italy", + "jm": "Jamaica", + "jo": "Jordan", + "jp": "Japan", + "ke": "Kenya", + "kg": "Kyrgyzstan", + "kh": "Cambodia", + "kp": "Korea, Democratic People's Republic of", + "kr": "Korea, Republic of", + "kw": "Kuwait", + "kz": "Kazakhstan", + "la": "Lao People's Democratic Republic", + "lb": "Lebanon", + "li": "Liechtenstein", + "lk": "Sri Lanka", + "lr": "Liberia", + "ls": "Lesotho", + "lt": "Lithuania", + "lu": "Luxembourg", + "lv": "Latvia", + "ly": "Libyan Arab Jamahiriya", + "ma": "Morocco", + "mc": "Monaco", + "md": "Moldova, Republic of", + "me": "Montenegro", + "mg": "Madagascar", + "mk": "Macedonia, the former Yugoslav Republic of", + "ml": "Mali", + "mm": "Myanmar", + "mn": "Mongolia", + "mo": "Macao", + "mr": "Mauritania", + "mt": "Malta", + "mu": "Mauritius", + "mv": "Maldives", + "mw": "Malawi", + "mx": "Mexico", + "my": "Malaysia", + "mz": "Mozambique", + "na": "Namibia", + "ne": "Niger", + "ng": "Nigeria", + "ni": "Nicaragua", + "nl": "Netherlands", + "no": "Norway", + "np": "Nepal", + "nz": "New Zealand", + "om": "Oman", + "pa": "Panama", + "pe": "Peru", + "pg": "Papua New Guinea", + "ph": "Philippines", + "pk": "Pakistan", + "pl": "Poland", + "pr": "Puerto Rico", + "ps": "Palestine, State of", + "pt": "Portugal", + "py": "Paraguay", + "re": "Reunion", + "ro": "Romania", + "rs": "Serbia", + "ru": "Russian Federation", + "rw": "Rwanda", + "sa": "Saudi Arabia", + "sc": "Seychelles", + "sd": "Sudan", + "se": "Sweden", + "sg": "Singapore", + "sh": "Saint Helena, Ascension and Tristan da Cunha", + "si": "Slovenia", + "sk": "Slovakia", + "sl": "Sierra Leone", + "sm": "San Marino", + "sn": "Senegal", + "so": "Somalia", + "sr": "Suriname", + "st": "Sao Tome and Principe", + "sv": "El Salvador", + "sy": "Syrian Arab Republic", + "sz": "Swaziland", + "td": "Chad", + "tg": "Togo", + "th": "Thailand", + "tj": "Tajikistan", + "tl": "Timor-Leste", + "tm": "Turkmenistan", + "tn": "Tunisia", + "tr": "Turkey", + "tw": "Taiwan (Republic of China)", + "tz": "Tanzania, United Republic of", + "ua": "Ukraine", + "ug": "Uganda", + "us": "United States", + "uy": "Uruguay", + "uz": "Uzbekistan", + "va": "Holy See (Vatican City State)", + "ve": "Venezuela, Bolivarian Republic of", + "vn": "Viet Nam", + "ye": "Yemen", + "yt": "Mayotte", + "za": "South Africa", + "zm": "Zambia", + "zw": "Zimbabwe" +} \ No newline at end of file diff --git a/buffalogs/impossible_travel/tests/setup.py b/buffalogs/impossible_travel/tests/setup.py index 53dfb9b..cbf401d 100644 --- a/buffalogs/impossible_travel/tests/setup.py +++ b/buffalogs/impossible_travel/tests/setup.py @@ -1,4 +1,4 @@ -from impossible_travel.models import Alert, Config, Login, TaskSettings, User +from impossible_travel.models import Config, Login, User class Setup: diff --git a/buffalogs/impossible_travel/tests/test_login_from_new_country.py b/buffalogs/impossible_travel/tests/test_login_from_new_country.py index 9a4dc93..f9e888b 100644 --- a/buffalogs/impossible_travel/tests/test_login_from_new_country.py +++ b/buffalogs/impossible_travel/tests/test_login_from_new_country.py @@ -1,6 +1,6 @@ from django.test import TestCase from impossible_travel.models import Login, User -from impossible_travel.modules import impossible_travel, login_from_new_country +from impossible_travel.modules import login_from_new_country class TestLoginFromNewCountry(TestCase): diff --git a/buffalogs/impossible_travel/tests/test_tasks.py b/buffalogs/impossible_travel/tests/test_tasks.py index 07c633e..dfe68aa 100644 --- a/buffalogs/impossible_travel/tests/test_tasks.py +++ b/buffalogs/impossible_travel/tests/test_tasks.py @@ -6,7 +6,6 @@ from django.utils import timezone from impossible_travel import tasks from impossible_travel.models import Alert, Config, Login, TaskSettings, User, UsersIP -from impossible_travel.modules import impossible_travel from impossible_travel.tests.setup import Setup diff --git a/buffalogs/impossible_travel/tests/test_views.py b/buffalogs/impossible_travel/tests/test_views.py new file mode 100644 index 0000000..541e97d --- /dev/null +++ b/buffalogs/impossible_travel/tests/test_views.py @@ -0,0 +1,204 @@ +import json +from datetime import datetime, timedelta + +from django.test import Client +from django.urls import reverse +from impossible_travel.models import Alert, Login, User +from rest_framework.test import APITestCase + + +class TestViews(APITestCase): + def setUp(self): + self.client = Client() + User.objects.bulk_create( + [ + User(username="Lorena Goldoni", risk_score=User.riskScoreEnum.NO_RISK), + User(username="Lorygold", risk_score=User.riskScoreEnum.LOW), + User(username="Lory", risk_score=User.riskScoreEnum.LOW), + User(username="Lor", risk_score=User.riskScoreEnum.LOW), + User(username="Loryg", risk_score=User.riskScoreEnum.MEDIUM), + ] + ) + db_user = User.objects.get(username="Lorena Goldoni") + Login.objects.bulk_create( + [ + Login( + user=db_user, + event_id="vfraw14gw", + index="cloud", + ip="1.2.3.4", + timestamp="2023-06-19T10:01:33.358Z", + latitude=40.364, + longitude=-79.8605, + country="United States", + user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/78.0.3904.108 Chrome/78.0.3904.108 Safari/537.36", + ), + Login( + user=db_user, + event_id="ht9DEIgBnkLiMp6r-SG-", + index="weblog", + ip="203.0.113.24", + timestamp="2023-06-19T10:08:33.358Z", + latitude=36.2462, + longitude=138.8497, + country="Japan", + user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0", + ), + Login( + user=db_user, + event_id="vfraw14gw", + index="cloud", + ip="1.2.3.4", + timestamp="2023-06-20T10:01:33.358Z", + latitude=40.364, + longitude=-79.8605, + country="United States", + user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/78.0.3904.108 Chrome/78.0.3904.108 Safari/537.36", + ), + Login( + user=db_user, + event_id="ht9DEIgBnkLiMp6r-SG-", + index="weblog", + ip="203.0.113.24", + timestamp="2023-06-20T10:08:33.358Z", + latitude=36.2462, + longitude=138.8497, + country="Japan", + user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0", + ), + ] + ) + Alert.objects.bulk_create( + [ + Alert( + user=db_user, + name=Alert.ruleNameEnum.NEW_DEVICE, + login_raw_data={ + "id": "ht9DEIgBnkLiMp6r-SG-", + "ip": "203.0.113.24", + "lat": 36.2462, + "lon": 138.8497, + "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0", + "index": "weblog", + "country": "Japan", + "timestamp": "2023-05-20T11:45:01.229Z", + }, + description="Test_Description0", + ), + Alert( + user=db_user, + name=Alert.ruleNameEnum.IMP_TRAVEL, + login_raw_data={ + "id": "vfraw14gw", + "ip": "1.2.3.4", + "lat": 40.364, + "lon": -79.8605, + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/78.0.3904.108 Chrome/78.0.3904.108 Safari/537.36", + "index": "cloud", + "country": "United States", + "timestamp": "2023-06-19T17:17:31.358Z", + }, + description="Test_Description1", + ), + Alert( + user=db_user, + name=Alert.ruleNameEnum.IMP_TRAVEL, + login_raw_data={ + "id": "vfraw14gw", + "ip": "1.2.3.4", + "lat": 40.364, + "lon": -79.8605, + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/78.0.3904.108 Chrome/78.0.3904.108 Safari/537.36", + "index": "cloud", + "country": "United States", + "timestamp": "2023-06-19T18:15:33.548Z", + }, + description="Test_Descriptio2", + ), + Alert( + user=db_user, + name=Alert.ruleNameEnum.IMP_TRAVEL, + login_raw_data={ + "id": "vfraw14gw", + "ip": "1.2.3.4", + "lat": 40.364, + "lon": -79.8605, + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/78.0.3904.108 Chrome/78.0.3904.108 Safari/537.36", + "index": "cloud", + "country": "United States", + "timestamp": "2023-06-20T10:17:33.358Z", + }, + description="Test_Description3", + ), + Alert( + user=db_user, + name=Alert.ruleNameEnum.NEW_DEVICE, + login_raw_data={ + "id": "ht9DEIgBnkLiMp6r-SG-", + "ip": "203.0.113.24", + "lat": 36.2462, + "lon": 138.8497, + "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0", + "index": "weblog", + "country": "Japan", + "timestamp": "2023-06-20T11:31:10.149Z", + }, + description="Test_Description4", + ), + Alert( + user=db_user, + name=Alert.ruleNameEnum.NEW_DEVICE, + login_raw_data={ + "id": "ht9DEIgBnkLiMp6r-SG-", + "ip": "203.0.113.24", + "lat": 36.2462, + "lon": 138.8497, + "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0", + "index": "weblog", + "country": "Japan", + "timestamp": "2023-06-20T11:45:01.229Z", + }, + description="Test_Description5", + ), + ] + ) + + def test_users_pie_chart_api(self): + end = datetime.now() + timedelta(minutes=1) + start = end - timedelta(hours=3) + dict_expected_result = {"no_risk": 1, "low": 3, "medium": 1, "high": 0} + response = self.client.get(f"{reverse('users_pie_chart_api')}?start={start.strftime('%Y-%m-%dT%H:%M:%SZ')}&end={end.strftime('%Y-%m-%dT%H:%M:%SZ')}") + self.assertEqual(response.status_code, 200) + self.assertDictEqual(dict_expected_result, json.loads(response.content)) + + def test_alerts_line_chart_api_hour(self): + start = datetime(2023, 6, 20, 10, 0) + end = datetime(2023, 6, 20, 12, 0) + dict_expected_result = {"Timeframe": "hour", "2023-06-20T10:00:00Z": 1, "2023-06-20T11:00:00Z": 2} + response = self.client.get(f"{reverse('alerts_line_chart_api')}?start={start.strftime('%Y-%m-%dT%H:%M:%SZ')}&end={end.strftime('%Y-%m-%dT%H:%M:%SZ')}") + self.assertEqual(response.status_code, 200) + self.assertDictEqual(dict_expected_result, json.loads(response.content)) + + def test_alerts_line_chart_api_day(self): + start = datetime(2023, 6, 19, 0, 0) + end = datetime(2023, 6, 20, 23, 59, 59) + dict_expected_result = {"Timeframe": "day", "2023-6-19": 2, "2023-6-20": 3} + response = self.client.get(f"{reverse('alerts_line_chart_api')}?start={start.strftime('%Y-%m-%dT%H:%M:%SZ')}&end={end.strftime('%Y-%m-%dT%H:%M:%SZ')}") + self.assertEqual(response.status_code, 200) + self.assertDictEqual(dict_expected_result, json.loads(response.content)) + + def test_alerts_line_chart_api_month(self): + start = datetime(2023, 5, 1, 0, 0) + end = datetime(2023, 6, 30, 23, 59, 59) + dict_expected_result = {"Timeframe": "month", "2023-5": 1, "2023-6": 5} + response = self.client.get(f"{reverse('alerts_line_chart_api')}?start={start.strftime('%Y-%m-%dT%H:%M:%SZ')}&end={end.strftime('%Y-%m-%dT%H:%M:%SZ')}") + self.assertEqual(response.status_code, 200) + self.assertDictEqual(dict_expected_result, json.loads(response.content)) + + def test_world_map_chart_api(self): + start = datetime(2023, 5, 1, 0, 0) + end = datetime(2023, 6, 30, 23, 59, 59) + dict_expected_result = {"us": 3, "jp": 3} + response = self.client.get(f"{reverse('world_map_chart_api')}?start={start.strftime('%Y-%m-%dT%H:%M:%SZ')}&end={end.strftime('%Y-%m-%dT%H:%M:%SZ')}") + self.assertEqual(response.status_code, 200) + self.assertDictEqual(dict_expected_result, json.loads(response.content)) diff --git a/buffalogs/impossible_travel/views.py b/buffalogs/impossible_travel/views.py index 3bcbd7e..2547386 100644 --- a/buffalogs/impossible_travel/views.py +++ b/buffalogs/impossible_travel/views.py @@ -1,17 +1,28 @@ +import calendar import json -from datetime import timedelta +import os +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta from django.conf import settings from django.db.models import Count, Max -from django.http import JsonResponse +from django.http import HttpResponse, JsonResponse from django.shortcuts import render from django.utils import timezone from django.utils.dateparse import parse_datetime +from django.views.decorators.http import require_http_methods from elasticsearch_dsl import Search, connections from impossible_travel.dashboard.charts import alerts_line_chart, users_pie_chart, world_map_chart from impossible_travel.models import Alert, Login, User +def _load_data(name): + DATA_PATH = "impossible_travel/dashboard/" + with open(os.path.join(DATA_PATH, name + ".json")) as file: + data = json.load(file) + return data + + def homepage(request): if request.method == "GET": date_range = [] @@ -153,3 +164,84 @@ def get_all_logins(request, pk_user): tmp["user_agent"] = "" context.append(tmp) return JsonResponse(json.dumps(context, default=str), safe=False) + + +@require_http_methods(["GET"]) +def users_pie_chart_api(request): + timestamp_format = "%Y-%m-%dT%H:%M:%SZ" + start_date = datetime.strptime(request.GET.get("start", ""), timestamp_format) + end_date = datetime.strptime(request.GET.get("end", ""), timestamp_format) + result = { + "no_risk": User.objects.filter(updated__range=(start_date, end_date), risk_score="No risk").count(), + "low": User.objects.filter(updated__range=(start_date, end_date), risk_score="Low").count(), + "medium": User.objects.filter(updated__range=(start_date, end_date), risk_score="Medium").count(), + "high": User.objects.filter(updated__range=(start_date, end_date), risk_score="High").count(), + } + data = json.dumps(result) + return HttpResponse(data, content_type="json") + + +@require_http_methods(["GET"]) +def alerts_line_chart_api(request): + timestamp_format = "%Y-%m-%dT%H:%M:%SZ" + start_date = datetime.strptime(request.GET.get("start", ""), timestamp_format) + end_date = datetime.strptime(request.GET.get("end", ""), timestamp_format) + date_range = [] + date_str = [] + result = {} + delta_timestamp = end_date - start_date + if delta_timestamp.days < 1: + result["Timeframe"] = "hour" + while start_date <= end_date: + date_range.append(start_date) + date_str.append(start_date.strftime("%Y-%m-%dT%H:%M:%SZ")) + start_date = start_date + timedelta(minutes=59, seconds=59) + date_range.append(start_date) + start_date = start_date + timedelta(seconds=1) + for i in range(0, len(date_str) - 1, 1): + result[date_str[i]] = Alert.objects.filter(login_raw_data__timestamp__range=(date_str[i], date_str[i + 1])).count() + elif delta_timestamp.days >= 1 and delta_timestamp.days <= 31: + result["Timeframe"] = "day" + while start_date.day < end_date.day: + start_date = datetime(start_date.year, start_date.month, start_date.day, 0, 0) + date_range.append(start_date) + start_date = start_date + timedelta(hours=23, minutes=59, seconds=59) + date_range.append(start_date) + start_date = start_date + timedelta(seconds=1) + start_date = datetime(start_date.year, start_date.month, start_date.day, 0, 0) + date_range.append(start_date) + date_range.append(end_date) + for i in range(0, len(date_range) - 1, 2): + date = str(date_range[i].year) + "-" + str(date_range[i].month) + "-" + str(date_range[i].day) + result[date] = Alert.objects.filter(login_raw_data__timestamp__range=(date_range[i].isoformat(), date_range[i + 1].isoformat())).count() + else: + result["Timeframe"] = "month" + start_date = timezone.datetime(start_date.year, start_date.month, 1) + while start_date <= end_date: + date_range.append(datetime(start_date.year, start_date.month, 1)) + date_range.append(datetime(start_date.year, start_date.month, calendar.monthrange(start_date.year, start_date.month)[1])) + date_str.append(start_date.strftime("%Y-%m")) + start_date = start_date + relativedelta(months=1) + for i in range(0, len(date_range) - 1, 2): + date = str(date_range[i].year) + "-" + str(date_range[i].month) + result[date] = Alert.objects.filter(login_raw_data__timestamp__range=(date_range[i].isoformat(), date_range[i + 1].isoformat())).count() + data = json.dumps(result) + return HttpResponse(data, content_type="json") + + +@require_http_methods(["GET"]) +def world_map_chart_api(request): + timestamp_format = "%Y-%m-%dT%H:%M:%SZ" + start_date = datetime.strptime(request.GET.get("start", ""), timestamp_format) + end_date = datetime.strptime(request.GET.get("end", ""), timestamp_format) + countries = _load_data("countries") + result = {} + for key, value in countries.items(): + country_alerts = Alert.objects.filter( + login_raw_data__timestamp__range=(start_date.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), end_date.strftime("%Y-%m-%dT%H:%M:%S.%fZ")), + login_raw_data__country=value, + ).count() + if country_alerts != 0: + result[key] = country_alerts + data = json.dumps(result) + return HttpResponse(data, content_type="json") diff --git a/buffalogs/requirements.txt b/buffalogs/requirements.txt index ceee07f..420b2a6 100644 --- a/buffalogs/requirements.txt +++ b/buffalogs/requirements.txt @@ -15,6 +15,7 @@ djangorestframework djangorestframework-simplejwt django-cors-headers django-environ==0.9.0 +djangorestframework==3.14.0 elasticsearch==7.17.7 elasticsearch-dsl==7.4.0 filelock==3.9.0