diff --git a/.github/actions/services/action.yml b/.github/actions/services/action.yml index c48dd60..44bdd44 100644 --- a/.github/actions/services/action.yml +++ b/.github/actions/services/action.yml @@ -154,9 +154,9 @@ runs: shell: bash - - name: Execute docker-compose up + - name: Execute docker compose up run: | - CLI=docker-compose + CLI="docker compose" if [[ ${{ inputs.use_postgres }} != 'false' ]]; then CLI="${CLI} -f postgres.yml" fi diff --git a/.github/configurations/python_linters/.flake8 b/.github/configurations/python_linters/.flake8 index 4fe5917..3707569 100644 --- a/.github/configurations/python_linters/.flake8 +++ b/.github/configurations/python_linters/.flake8 @@ -16,4 +16,8 @@ ignore = exclude = */migrations/*, - Dockerfile \ No newline at end of file + Dockerfile + +per-file-ignores = + # imported but unused + certego.py: E231 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c1be8c2..613410d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,22 @@ +## 1.3.x +### 1.3.0 +#### Feature +* Added configuration panel in order to set custom preferences +* Added more fields in the Alert.login_raw_data dict in order to have more info about previous location for imp_travel detection +### Changes +* Set default settings values in the *settings.certego.py* file +* Moved Enums into *costants.py* file + ## 1.2.x +### 1.2.12 +#### Bugfix +* Cleaned venv from useless packages +* Added pytz in requirements because it's needed by celery_beat +* Registered UserAdmin in authentication +### 1.2.11 +#### Bugfix +* Fixed the update of the login.updated field +* Added logging for the clear_models_periodically function ### 1.2.10 #### Changes * Added settings into the Config model (instead of into the settings.py file) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a3e37d..7b6d9ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,15 +55,15 @@ How to create and submit a PR: If you didn't install pre-commit, it is necessary to run linters manually: * Flake8 ```bash - flake8 . --show-source --config ../.github/configurations/.flake8 + flake8 . --show-source --config ../.github/configurations/python_linters/.flake8 ``` * Black ```bash - black --config ../.github/configurations/.black . + black --config ../.github/configurations/python_linters/.black . ``` * Isort ```bash - isort --sp ../.github/configurations/.isort.cfg --profile black . + isort --sp ../.github/configurations/python_linters/.isort.cfg --profile black . ``` 3. **IF** your changes include differences in the template view, **include sceenshots of the before and after**. diff --git a/buffalogs/authentication/admin.py b/buffalogs/authentication/admin.py index 8c38f3f..1a5fc47 100644 --- a/buffalogs/authentication/admin.py +++ b/buffalogs/authentication/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin -# Register your models here. +from .models import User + + +@admin.register(User) +class UserAdmin(admin.ModelAdmin): + list_display = ("id", "username", "email", "is_staff", "is_verified", "created_at", "updated_at", "avatar") + search_fields = ("id", "username", "email", "avatar") diff --git a/buffalogs/authentication/models.py b/buffalogs/authentication/models.py index 2cf02e1..37e68d0 100644 --- a/buffalogs/authentication/models.py +++ b/buffalogs/authentication/models.py @@ -49,6 +49,3 @@ def __str__(self): def tokens(self): refresh = RefreshToken.for_user(self) return {"refresh": str(refresh), "access": str(refresh.access_token)} - - -# Create your models here. diff --git a/buffalogs/buffalogs/settings/certego.py b/buffalogs/buffalogs/settings/certego.py index 66d2ec0..ec4919c 100644 --- a/buffalogs/buffalogs/settings/certego.py +++ b/buffalogs/buffalogs/settings/certego.py @@ -15,6 +15,17 @@ CERTEGO_BUFFALOGS_POSTGRES_PORT = os.environ.get("BUFFALOGS_POSTGRES_PORT", "5432") CERTEGO_BUFFALOGS_ELASTIC_INDEX = os.environ.get("BUFFALOGS_ELASTIC_INDEX", "weblog-*,cloud-*,fw-proxy-*,filebeat-*") CERTEGO_BUFFALOGS_SECRET_KEY = os.environ.get("BUFFALOGS_SECRET_KEY", "django-insecure-am9z-fi-x*aqxlb-@abkhb@pu!0da%0a77h%-8d(dwzrrktwhu") +CERTEGO_BUFFALOGS_IGNORED_USERS = ["Not Available", "N/A"] +CERTEGO_BUFFALOGS_ENABLED_USERS = [] +CERTEGO_BUFFALOGS_ALLOWED_COUNTRIES = [] +CERTEGO_BUFFALOGS_IGNORED_IPS = ["127.0.0.1"] +CERTEGO_BUFFALOGS_VIP_USERS = [] +CERTEGO_BUFFALOGS_DISTANCE_KM_ACCEPTED = 100 +CERTEGO_BUFFALOGS_VEL_TRAVEL_ACCEPTED = 300 +CERTEGO_BUFFALOGS_USER_MAX_DAYS = 60 +CERTEGO_BUFFALOGS_LOGIN_MAX_DAYS = 30 +CERTEGO_BUFFALOGS_ALERT_MAX_DAYS = 30 +CERTEGO_BUFFALOGS_IP_MAX_DAYS = 30 if CERTEGO_BUFFALOGS_ENVIRONMENT == ENVIRONMENT_DOCKER: diff --git a/buffalogs/celerybeat-schedule b/buffalogs/celerybeat-schedule index 4073711..e965372 100644 Binary files a/buffalogs/celerybeat-schedule and b/buffalogs/celerybeat-schedule differ diff --git a/buffalogs/impossible_travel/constants.py b/buffalogs/impossible_travel/constants.py new file mode 100644 index 0000000..ba3e938 --- /dev/null +++ b/buffalogs/impossible_travel/constants.py @@ -0,0 +1,89 @@ +from enum import Enum + + +class UserRiskScoreType(Enum): + """Possible types of user risk scores, based on number of alerts that they have triggered + + * No risk: the user has triggered 0 alerts + * Low: the user has triggered 1 or 2 alerts + * Medium: the user has triggered 3 or 4 alerts + * High: the user has triggered more than 4 alerts + """ + + NO_RISK = "No risk" + LOW = "Low" + MEDIUM = "Medium" + HIGH = "High" + + @classmethod + def choices(cls): + return tuple((i.name, i.value) for i in cls) + + @classmethod + def get_risk_level(cls, value): + # map risk value + if value == 0: + return cls.NO_RISK.value + elif 1 <= value <= 2: + return cls.LOW.value + elif 3 <= value <= 4: + return cls.MEDIUM.value + elif value >= 5: + return cls.HIGH.value + else: + raise ValueError("Risk value not valid") + + +class AlertDetectionType(Enum): + """Types of possible alert detections + + * NEW_DEVICE: Login from a new user-agent used by the user + * IMP_TRAVEL: Alert if the user logs into the system from a significant distance () within a range of time that cannot be covered by conventional means of transport + * NEW_COUNTRY: The user made a login from a country where they have never logged in before + * USER_RISK_THRESHOLD: + * LOGIN_ANONYMIZER_IP: + * ATYPICAL_COUNTRY + """ + + NEW_DEVICE = "Login from new device" + IMP_TRAVEL = "Impossible Travel detected" + NEW_COUNTRY = "Login from new country" + USER_RISK_THRESHOLD = "User risk threshold alert" + LOGIN_ANONYMIZER_IP = "Login from anonymizer IP" + ATYPICAL_COUNTRY = "Login from atypical country" + + @classmethod + def choices(cls): + return tuple((i.name, i.value) for i in cls) + + @classmethod + def get_label_from_value(cls, value): + for item in cls: + if item.value == value: + return item.name + return None + + +class AlertFilterType(Enum): + """Types of possible detection filter applied on alerts to be ignored + + * ISP_FILTER: exclude from the detection a list of whitelisted ISP + * IS_MOBILE_FILTER: if Config.ignore_mobile_logins flag is checked, exclude from the detection the mobile devices + * IS_VIP_FILTER: if Config.alert_is_vip_only flag is checked, only the vip users (in the Config.vip_users list) send alerts + * ALLOWED_COUNTRY_FILTER: if the country of the login is in the Config.allowed_countries list, the alert isn't sent + * IGNORED_USER_FILTER: if the user is in the Config.ignored_users list OR the user is not in the Config.enabled_users list, the alert isn't sent + * ALERT_MINIMUM_RISK_SCORE_FILTER: if the user hasn't, at least, a User.risk_score equals to the one sets in Config.alert_minimum_risk_score, + * FILTERED_ALERTS: if the alert type (AlertDetectionType) is in the Config.filtered_alerts, the alert isn't sent + """ + + ISP_FILTER = "isp_filter" + IS_MOBILE_FILTER = "is_mobile_filter" + IS_VIP_FILTER = "is_vip_filter" + ALLOWED_COUNTRY_FILTER = "allowed_country_filter" + IGNORED_USER_FILTER = "ignored_user_filter" + ALERT_MINIMUM_RISK_SCORE_FILTER = "alert_minimum_risk_score_filter" + FILTERED_ALERTS = "filtered_alerts" + + @classmethod + def choices(cls): + return tuple((i.name, i.value) for i in cls) diff --git a/buffalogs/impossible_travel/migrations/0011_alert_filter_type_alert_is_filtered_and_more.py b/buffalogs/impossible_travel/migrations/0011_alert_filter_type_alert_is_filtered_and_more.py new file mode 100644 index 0000000..e77e113 --- /dev/null +++ b/buffalogs/impossible_travel/migrations/0011_alert_filter_type_alert_is_filtered_and_more.py @@ -0,0 +1,188 @@ +# Generated by Django 5.1.4 on 2024-12-13 10:25 + +import django.contrib.postgres.fields +import impossible_travel.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "impossible_travel", + "0010_config_alert_max_days_config_distance_accepted_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="alert", + name="filter_type", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + blank=True, + choices=[ + ("ISP_FILTER", "isp_filter"), + ("IS_MOBILE_FILTER", "is_mobile_filter"), + ("IS_VIP_FILTER", "is_vip_filter"), + ("ALLOWED_COUNTRY_FILTER", "allowed_country_filter"), + ("IGNORED_USER_FILTER", "ignored_user_filter"), + ( + "ALERT_MINIMUM_RISK_SCORE_FILTER", + "alert_minimum_risk_score_filter", + ), + ("FILTERED_ALERTS", "filtered_alerts"), + ], + max_length=50, + ), + blank=True, + default=list, + help_text="List of filters that disabled the related alert", + size=None, + ), + ), + migrations.AddField( + model_name="alert", + name="is_filtered", + field=models.BooleanField( + default=False, + help_text="Show if the alert has been filtered because of some filter (listed in the filter_type field)", + ), + ), + migrations.AddField( + model_name="config", + name="alert_is_vip_only", + field=models.BooleanField( + default=False, + help_text="Flag to send alert only related to the users in the vip_users list", + ), + ), + migrations.AddField( + model_name="config", + name="alert_minimum_risk_score", + field=models.CharField( + choices=[ + ("NO_RISK", "No risk"), + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ], + default="No risk", + help_text="Select the risk_score that users should have at least to send alert", + max_length=30, + ), + ), + migrations.AddField( + model_name="config", + name="enabled_users", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=50), + blank=True, + default=impossible_travel.models.get_default_enabled_users, + help_text="List of selected users on which the detection will perform", + size=None, + ), + ), + migrations.AddField( + model_name="config", + name="filtered_alerts_types", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + blank=True, + choices=[ + ("NEW_DEVICE", "Login from new device"), + ("IMP_TRAVEL", "Impossible Travel detected"), + ("NEW_COUNTRY", "Login from new country"), + ("USER_RISK_THRESHOLD", "User risk threshold alert"), + ("LOGIN_ANONYMIZER_IP", "Login from anonymizer IP"), + ("ATYPICAL_COUNTRY", "Login from atypical country"), + ], + max_length=50, + ), + default=list, + help_text="List of alerts' types to exclude from the alerting", + size=None, + ), + ), + migrations.AddField( + model_name="config", + name="ignore_mobile_logins", + field=models.BooleanField( + default=False, + help_text="Flag to ignore mobile devices from the detection", + ), + ), + migrations.AlterField( + model_name="alert", + name="name", + field=models.CharField( + choices=[ + ("NEW_DEVICE", "Login from new device"), + ("IMP_TRAVEL", "Impossible Travel detected"), + ("NEW_COUNTRY", "Login from new country"), + ("USER_RISK_THRESHOLD", "User risk threshold alert"), + ("LOGIN_ANONYMIZER_IP", "Login from anonymizer IP"), + ("ATYPICAL_COUNTRY", "Login from atypical country"), + ], + max_length=30, + ), + ), + migrations.AlterField( + model_name="config", + name="allowed_countries", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=20), + blank=True, + default=impossible_travel.models.get_default_allowed_countries, + help_text="List of countries to exclude from the detection, because 'trusted' for the customer", + size=None, + ), + ), + migrations.AlterField( + model_name="config", + name="ignored_ips", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=50), + blank=True, + default=impossible_travel.models.get_default_ignored_ips, + help_text="List of IPs to remove from the detection", + size=None, + ), + ), + migrations.AlterField( + model_name="config", + name="ignored_users", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=50), + blank=True, + default=impossible_travel.models.get_default_ignored_users, + help_text="List of users to be ignored from the detection", + size=None, + ), + ), + migrations.AlterField( + model_name="config", + name="vip_users", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=50), + blank=True, + default=impossible_travel.models.get_default_vip_users, + help_text="List of users considered more sensitive", + size=None, + ), + ), + migrations.AlterField( + model_name="user", + name="risk_score", + field=models.CharField( + choices=[ + ("NO_RISK", "No risk"), + ("LOW", "Low"), + ("MEDIUM", "Medium"), + ("HIGH", "High"), + ], + default="No risk", + max_length=30, + ), + ), + ] diff --git a/buffalogs/impossible_travel/models.py b/buffalogs/impossible_travel/models.py index e3605c4..87a2ee9 100644 --- a/buffalogs/impossible_travel/models.py +++ b/buffalogs/impossible_travel/models.py @@ -1,23 +1,18 @@ +from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.db import models +from impossible_travel.constants import AlertDetectionType, AlertFilterType, UserRiskScoreType class User(models.Model): - class riskScoreEnum(models.TextChoices): - NO_RISK = "No risk" - LOW = "Low" - MEDIUM = "Medium" - HIGH = "High" - - risk_score = models.CharField( - choices=riskScoreEnum.choices, - max_length=256, - null=False, - ) + risk_score = models.CharField(choices=UserRiskScoreType.choices(), max_length=30, null=False, default=UserRiskScoreType.NO_RISK.value) username = models.TextField(unique=True, db_index=True) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) + def get_risk_display(self): + return AlertFilterType.get_risk_level(self.risk_level).name + class Login(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) @@ -34,14 +29,9 @@ class Login(models.Model): class Alert(models.Model): - class ruleNameEnum(models.TextChoices): - NEW_DEVICE = "Login from new device" - IMP_TRAVEL = "Impossible Travel detected" - NEW_COUNTRY = "Login from new country" - name = models.CharField( - choices=ruleNameEnum.choices, - max_length=256, + choices=AlertDetectionType.choices(), + max_length=30, null=False, ) user = models.ForeignKey(User, on_delete=models.CASCADE) @@ -50,6 +40,13 @@ class ruleNameEnum(models.TextChoices): updated = models.DateTimeField(auto_now=True) description = models.TextField() is_vip = models.BooleanField(default=False) + is_filtered = models.BooleanField(default=False, help_text="Show if the alert has been filtered because of some filter (listed in the filter_type field)") + filter_type = ArrayField( + models.CharField(max_length=50, choices=AlertFilterType.choices(), blank=True), + blank=True, + default=list, + help_text="List of filters that disabled the related alert", + ) class UsersIP(models.Model): @@ -67,20 +64,72 @@ class TaskSettings(models.Model): end_date = models.DateTimeField() +def get_default_ignored_users(): + return list(settings.CERTEGO_BUFFALOGS_IGNORED_USERS) + + +def get_default_enabled_users(): + return list(settings.CERTEGO_BUFFALOGS_ENABLED_USERS) + + +def get_default_ignored_ips(): + return list(settings.CERTEGO_BUFFALOGS_IGNORED_IPS) + + +def get_default_allowed_countries(): + return list(settings.CERTEGO_BUFFALOGS_ALLOWED_COUNTRIES) + + +def get_default_vip_users(): + return list(settings.CERTEGO_BUFFALOGS_VIP_USERS) + + class Config(models.Model): created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) - ignored_users = ArrayField(models.CharField(max_length=20), blank=True, default=list) - ignored_ips = ArrayField(models.CharField(max_length=100), blank=True, default=list) - allowed_countries = ArrayField(models.CharField(max_length=20), blank=True, default=list) - vip_users = ArrayField(models.CharField(max_length=100), blank=True, default=list) + ignored_users = ArrayField( + models.CharField(max_length=50), blank=True, default=get_default_ignored_users, help_text="List of users to be ignored from the detection" + ) + enabled_users = ArrayField( + models.CharField(max_length=50), blank=True, default=get_default_enabled_users, help_text="List of selected users on which the detection will perform" + ) + ignored_ips = ArrayField(models.CharField(max_length=50), blank=True, default=get_default_ignored_ips, help_text="List of IPs to remove from the detection") + allowed_countries = ArrayField( + models.CharField(max_length=20), + blank=True, + default=get_default_allowed_countries, + help_text="List of countries to exclude from the detection, because 'trusted' for the customer", + ) + vip_users = ArrayField(models.CharField(max_length=50), blank=True, default=get_default_vip_users, help_text="List of users considered more sensitive") + alert_is_vip_only = models.BooleanField(default=False, help_text="Flag to send alert only related to the users in the vip_users list") + alert_minimum_risk_score = models.CharField( + choices=UserRiskScoreType.choices(), + max_length=30, + blank=False, + default=UserRiskScoreType.NO_RISK.value, + help_text="Select the risk_score that users should have at least to send alert", + ) + filtered_alerts_types = ArrayField( + models.CharField(max_length=50, choices=AlertDetectionType.choices(), blank=True), + default=list, + help_text="List of alerts' types to exclude from the alerting", + ) + ignore_mobile_logins = models.BooleanField(default=False, help_text="Flag to ignore mobile devices from the detection") distance_accepted = models.PositiveIntegerField( - default=100, help_text="Minimum distance (in Km) between two logins after which the impossible travel detection starts" + default=settings.CERTEGO_BUFFALOGS_DISTANCE_KM_ACCEPTED, + help_text="Minimum distance (in Km) between two logins after which the impossible travel detection starts", ) vel_accepted = models.PositiveIntegerField( - default=300, help_text="Minimum velocity (in Km/h) between two logins after which the impossible travel detection starts" + default=settings.CERTEGO_BUFFALOGS_VEL_TRAVEL_ACCEPTED, + help_text="Minimum velocity (in Km/h) between two logins after which the impossible travel detection starts", + ) + user_max_days = models.PositiveIntegerField( + default=settings.CERTEGO_BUFFALOGS_USER_MAX_DAYS, help_text="Days after which the users will be removed from the db" + ) + login_max_days = models.PositiveIntegerField( + default=settings.CERTEGO_BUFFALOGS_LOGIN_MAX_DAYS, help_text="Days after which the logins will be removed from the db" + ) + alert_max_days = models.PositiveIntegerField( + default=settings.CERTEGO_BUFFALOGS_ALERT_MAX_DAYS, help_text="Days after which the alerts will be removed from the db" ) - user_max_days = models.PositiveIntegerField(default=60, help_text="Days after which the users will be removed from the db") - login_max_days = models.PositiveIntegerField(default=30, help_text="Days after which the logins will be removed from the db") - alert_max_days = models.PositiveIntegerField(default=30, help_text="Days after which the alerts will be removed from the db") - ip_max_days = models.PositiveIntegerField(default=30, help_text="Days after which the IPs will be removed from the db") + ip_max_days = models.PositiveIntegerField(default=settings.CERTEGO_BUFFALOGS_IP_MAX_DAYS, help_text="Days after which the IPs will be removed from the db") diff --git a/buffalogs/impossible_travel/modules/impossible_travel.py b/buffalogs/impossible_travel/modules/impossible_travel.py index 290f8c2..f189577 100644 --- a/buffalogs/impossible_travel/modules/impossible_travel.py +++ b/buffalogs/impossible_travel/modules/impossible_travel.py @@ -3,6 +3,7 @@ from django.utils import timezone from geopy.distance import geodesic +from impossible_travel.constants import AlertDetectionType, UserRiskScoreType from impossible_travel.models import Alert, Config, Login, UsersIP @@ -44,7 +45,7 @@ def calc_distance(self, db_user, prev_login, last_login_user_fields): vel = distance_km / diff_timestamp_hours if vel > app_config.vel_accepted: - alert_info["alert_name"] = Alert.ruleNameEnum.IMP_TRAVEL + alert_info["alert_name"] = AlertDetectionType.IMP_TRAVEL.value alert_info[ "alert_desc" ] = f"{alert_info['alert_name']} for User: {db_user.username}, at: {last_timestamp_datetimeObj_aware}, from: {last_login_user_fields['country']}, previous country: {prev_login.country}, distance covered at {int(vel)} Km/h" diff --git a/buffalogs/impossible_travel/modules/login_from_new_country.py b/buffalogs/impossible_travel/modules/login_from_new_country.py index d66e689..7e8ef2e 100644 --- a/buffalogs/impossible_travel/modules/login_from_new_country.py +++ b/buffalogs/impossible_travel/modules/login_from_new_country.py @@ -1,5 +1,6 @@ import logging +from impossible_travel.constants import AlertDetectionType from impossible_travel.models import Alert from impossible_travel.modules import impossible_travel @@ -19,6 +20,6 @@ def check_country(self, db_user, login_field): new_country = login_field["country"] if db_user.login_set.filter(country=new_country).count() == 0: time = login_field["timestamp"] - alert_info["alert_name"] = Alert.ruleNameEnum.NEW_COUNTRY + alert_info["alert_name"] = AlertDetectionType.NEW_COUNTRY.value alert_info["alert_desc"] = f"{alert_info['alert_name']} for User: {db_user.username}, at: {time}, from: {new_country}" return alert_info diff --git a/buffalogs/impossible_travel/modules/login_from_new_device.py b/buffalogs/impossible_travel/modules/login_from_new_device.py index aa321cc..6b04ce4 100644 --- a/buffalogs/impossible_travel/modules/login_from_new_device.py +++ b/buffalogs/impossible_travel/modules/login_from_new_device.py @@ -1,5 +1,6 @@ import logging +from impossible_travel.constants import AlertDetectionType from impossible_travel.models import Alert from impossible_travel.modules import impossible_travel @@ -21,10 +22,6 @@ def check_new_device(self, db_user, login_field): alert_info = {} if db_user.login_set.filter(user_agent=login_field["agent"]).count() == 0: timestamp = login_field["timestamp"] - alert_info["alert_name"] = Alert.ruleNameEnum.NEW_DEVICE - alert_info[ - "alert_desc" - ] = f"LOGIN FROM NEW DEVICE\ - for User: {db_user.username},\ - at: {timestamp}" + alert_info["alert_name"] = AlertDetectionType.NEW_DEVICE.value + alert_info["alert_desc"] = f"LOGIN FROM NEW DEVICE for User: {db_user.username}, at: {timestamp}" return alert_info diff --git a/buffalogs/impossible_travel/tasks.py b/buffalogs/impossible_travel/tasks.py index 6e214f7..c424e18 100644 --- a/buffalogs/impossible_travel/tasks.py +++ b/buffalogs/impossible_travel/tasks.py @@ -7,6 +7,7 @@ from django.db.models import Count from django.utils import timezone from elasticsearch_dsl import Search, connections +from impossible_travel.constants import UserRiskScoreType from impossible_travel.models import Alert, Config, Login, TaskSettings, User, UsersIP from impossible_travel.modules import impossible_travel, login_from_new_country, login_from_new_device @@ -37,18 +38,10 @@ def update_risk_level(): with transaction.atomic(): for u in User.objects.annotate(Count("alert")): alerts_num = u.alert__count - if alerts_num == 0: - tmp = User.riskScoreEnum.NO_RISK - elif 1 <= alerts_num <= 2: - tmp = User.riskScoreEnum.LOW - elif 3 <= alerts_num <= 4: - tmp = User.riskScoreEnum.MEDIUM - else: - tmp = User.riskScoreEnum.HIGH - if u.risk_score != tmp: - # Added log only if it's updated, not always for each High risk user - logger.info(f"{User.riskScoreEnum.HIGH} risk level for User: {u.username}, {alerts_num} detected") + tmp = UserRiskScoreType.get_risk_level(alerts_num) if u.risk_score != tmp: + # Added log only if it's updated, not always for each High risk user + logger.info(f"Upgraded risk level for User: {u.username}, {alerts_num} detected") u.risk_score = tmp u.save() @@ -64,7 +57,7 @@ def set_alert(db_user, login_alert, alert_info): :type alert_info: dict """ logger.info( - f"ALERT {alert_info['alert_name']} for User:{db_user.username} at:{login_alert['timestamp']} from {login_alert['country']} from device:{login_alert['agent']}" + f"ALERT {alert_info['alert_name']} for User: {db_user.username} at: {login_alert['timestamp']} from {login_alert['country']} from device: {login_alert['agent']}" ) alert = Alert.objects.create(user_id=db_user.id, login_raw_data=login_alert, name=alert_info["alert_name"], description=alert_info["alert_desc"]) if Config.objects.filter(vip_users__contains=[db_user.username]): @@ -97,12 +90,16 @@ def check_fields(db_user, fields): set_alert(db_user, login_alert=login, alert_info=country_alert) if not db_user.usersip_set.filter(ip=login["ip"]).exists(): + last_user_login = db_user.login_set.latest("timestamp") logger.info(f"Calculating impossible travel: {login['id']}") - travel_alert, travel_vel = imp_travel.calc_distance(db_user, prev_login=db_user.login_set.latest("timestamp"), last_login_user_fields=login) + travel_alert, travel_vel = imp_travel.calc_distance(db_user, prev_login=last_user_login, last_login_user_fields=login) if travel_alert: new_alert = set_alert(db_user, login_alert=login, alert_info=travel_alert) - new_alert.login_raw_data["buffalogs.start_country"] = db_user.login_set.latest("timestamp").country - new_alert.login_raw_data["buffalogs.avg_speed"] = travel_vel + new_alert.login_raw_data["buffalogs"] = {} + new_alert.login_raw_data["buffalogs"]["start_country"] = last_user_login.country + new_alert.login_raw_data["buffalogs"]["avg_speed"] = travel_vel + new_alert.login_raw_data["buffalogs"]["start_lat"] = last_user_login.latitude + new_alert.login_raw_data["buffalogs"]["start_lon"] = last_user_login.longitude new_alert.save() # Add the new ip address from which the login comes to the db imp_travel.add_new_user_ip(db_user, login["ip"]) @@ -221,7 +218,7 @@ def exec_process_logs(start_date, end_date): :param end_date: End datetime :type end_date: datetime """ - logger.info(f"Starting at:{start_date} Finishing at:{end_date}") + logger.info(f"Starting at: {start_date} Finishing at: {end_date}") config, op_result = Config.objects.get_or_create() connections.create_connection(hosts=settings.CERTEGO_ELASTICSEARCH, timeout=90, verify_certs=False) s = ( diff --git a/buffalogs/impossible_travel/tests/test_impossible_travel.py b/buffalogs/impossible_travel/tests/test_impossible_travel.py index c6116c2..2019f17 100644 --- a/buffalogs/impossible_travel/tests/test_impossible_travel.py +++ b/buffalogs/impossible_travel/tests/test_impossible_travel.py @@ -56,7 +56,7 @@ def test_calc_distance_alert(self): db_user = User.objects.get(username="Lorena Goldoni") prev_login = Login.objects.get(id=db_user.id) result, vel = self.imp_travel.calc_distance(db_user, prev_login, last_login_user_fields) - self.assertEqual("Impossible Travel detected", result["alert_name"].value) + self.assertEqual("Impossible Travel detected", result["alert_name"]) self.assertIn("for User: Lorena Goldoni", result["alert_desc"]) self.assertIn("from: Sudan", result["alert_desc"]) self.assertIn("previous country: United States, distance covered at 10109599 Km/h", result["alert_desc"]) 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 f9e888b..6d7fb67 100644 --- a/buffalogs/impossible_travel/tests/test_login_from_new_country.py +++ b/buffalogs/impossible_travel/tests/test_login_from_new_country.py @@ -45,4 +45,4 @@ def test_check_country_alert(self): "user_agent": "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", } alert_result = self.new_country.check_country(db_user, last_login_user_fields) - self.assertEqual("Login from new country", alert_result["alert_name"].value) + self.assertEqual("Login from new country", alert_result["alert_name"]) diff --git a/buffalogs/impossible_travel/tests/test_login_from_new_device.py b/buffalogs/impossible_travel/tests/test_login_from_new_device.py index 51701eb..4ec088d 100644 --- a/buffalogs/impossible_travel/tests/test_login_from_new_device.py +++ b/buffalogs/impossible_travel/tests/test_login_from_new_device.py @@ -45,4 +45,4 @@ def test_check_new_device_alert(self): "agent": "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", } alert_result = self.new_device.check_new_device(db_user, last_login_user_fields) - self.assertEqual("Login from new device", alert_result["alert_name"].value) + self.assertEqual("Login from new device", alert_result["alert_name"]) diff --git a/buffalogs/impossible_travel/tests/test_tasks.py b/buffalogs/impossible_travel/tests/test_tasks.py index e8dcbfc..4ef9560 100644 --- a/buffalogs/impossible_travel/tests/test_tasks.py +++ b/buffalogs/impossible_travel/tests/test_tasks.py @@ -6,6 +6,7 @@ from django.test import TestCase from django.utils import timezone from impossible_travel import tasks +from impossible_travel.constants import AlertDetectionType from impossible_travel.models import Alert, Config, Login, TaskSettings, User, UsersIP from impossible_travel.tests.setup import Setup @@ -72,7 +73,7 @@ def test_update_risk_level_low(self): # 1 alert --> Low risk self.assertTrue(User.objects.filter(username="Lorena Goldoni").exists()) db_user = User.objects.get(username="Lorena Goldoni") - Alert.objects.create(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL, login_raw_data="Test", description="Test_Description") + Alert.objects.create(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data="Test", description="Test_Description") tasks.update_risk_level() db_user = User.objects.get(username="Lorena Goldoni") self.assertEqual("Low", db_user.risk_score) @@ -84,9 +85,9 @@ def test_update_risk_level_medium(self): db_user = User.objects.get(username="Lorena Goldoni") Alert.objects.bulk_create( [ - Alert(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL, login_raw_data="Test1", description="Test_Description1"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_DEVICE, login_raw_data="Test2", description="Test_Description2"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY, login_raw_data="Test3", description="Test_Description3"), + Alert(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data="Test1", description="Test_Description1"), + Alert(user=db_user, name=AlertDetectionType.NEW_DEVICE.value, login_raw_data="Test2", description="Test_Description2"), + Alert(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value, login_raw_data="Test3", description="Test_Description3"), ] ) tasks.update_risk_level() @@ -100,11 +101,11 @@ def test_update_risk_level_high(self): db_user = User.objects.get(username="Lorena Goldoni") Alert.objects.bulk_create( [ - Alert(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL, login_raw_data="Test1", description="Test_Description1"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_DEVICE, login_raw_data="Test2", description="Test_Description2"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY, login_raw_data="Test3", description="Test_Description3"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY, login_raw_data="Test4", description="Test_Description4"), - Alert(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY, login_raw_data="Test5", description="Test_Description5"), + Alert(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data="Test1", description="Test_Description1"), + Alert(user=db_user, name=AlertDetectionType.NEW_DEVICE.value, login_raw_data="Test2", description="Test_Description2"), + Alert(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value, login_raw_data="Test3", description="Test_Description3"), + Alert(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value, login_raw_data="Test4", description="Test_Description4"), + Alert(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value, login_raw_data="Test5", description="Test_Description5"), ] ) tasks.update_risk_level() @@ -117,15 +118,15 @@ def test_set_alert(self): db_login = Login.objects.get(user_agent="Mozilla/5.0 (X11;U; Linux i686; en-GB; rv:1.9.1) Gecko/20090624 Ubuntu/9.04 (jaunty) Firefox/3.5") timestamp = db_login.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") login_data = {"timestamp": timestamp, "latitude": "45.4758", "longitude": "9.2275", "country": db_login.country, "agent": db_login.user_agent} - name = Alert.ruleNameEnum.IMP_TRAVEL - desc = f"{name} for User: {db_user.username},\ - at: {timestamp}, from:({db_login.latitude}, {db_login.longitude})" + name = AlertDetectionType.IMP_TRAVEL.value + desc = f"{name} for User: {db_user.username}, \ + at: {timestamp}, from: ({db_login.latitude}, {db_login.longitude})" alert_info = { "alert_name": name, "alert_desc": desc, } tasks.set_alert(db_user, login_data, alert_info) - db_alert = Alert.objects.get(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL) + db_alert = Alert.objects.get(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value) self.assertIsNotNone(db_alert) self.assertEqual("Impossible Travel detected", db_alert.name) self.assertFalse(db_alert.is_vip) @@ -136,15 +137,15 @@ def test_set_alert_vip_user(self): db_login = Login.objects.filter(user=db_user).first() timestamp = db_login.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") login_data = {"timestamp": timestamp, "latitude": "45.4758", "longitude": "9.2275", "country": db_login.country, "agent": db_login.user_agent} - name = Alert.ruleNameEnum.IMP_TRAVEL - desc = f"{name} for User: {db_user.username},\ - at: {timestamp}, from:({db_login.latitude}, {db_login.longitude})" + name = AlertDetectionType.IMP_TRAVEL.value + desc = f"{name} for User: {db_user.username}, \ + at: {timestamp}, from: ({db_login.latitude}, {db_login.longitude})" alert_info = { "alert_name": name, "alert_desc": desc, } tasks.set_alert(db_user, login_data, alert_info) - db_alert = Alert.objects.get(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL) + db_alert = Alert.objects.get(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value) self.assertTrue(db_alert.is_vip) def test_process_logs_data_lost(self): @@ -274,9 +275,9 @@ def check_fields_alerts(self): # 5. at 2023-05-03T06:57:27.768Z alert IMP TRAVEL # 6. at 2023-05-03T07:10:23.154Z alert IMP TRAVEL self.assertEqual(6, Alert.objects.filter(user=db_user).count()) - self.assertEqual(2, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.NEW_DEVICE).count()) - self.assertEqual(1, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY).count()) - self.assertEqual(3, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL).count()) + self.assertEqual(2, Alert.objects.filter(user=db_user, name=AlertDetectionType.NEW_DEVICE.value).count()) + self.assertEqual(1, Alert.objects.filter(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value).count()) + self.assertEqual(3, Alert.objects.filter(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value).count()) self.assertEqual(0, Alert.objects.filter(user=db_user, is_vip=True).count()) # Adding "Aisha Delgado" to vip users @@ -290,9 +291,9 @@ def check_fields_alerts(self): # 10. at 2023-05-03T07:18:38.768Z alert IMP TRAVEL # 11. at 2023-05-03T07:20:36.154Z alert IMP TRAVEL tasks.check_fields(db_user, fields2) - self.assertEqual(4, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.NEW_DEVICE).count()) - self.assertEqual(1, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.NEW_COUNTRY).count()) - self.assertEqual(6, Alert.objects.filter(user=db_user, name=Alert.ruleNameEnum.IMP_TRAVEL).count()) + self.assertEqual(4, Alert.objects.filter(user=db_user, name=AlertDetectionType.NEW_DEVICE.value).count()) + self.assertEqual(1, Alert.objects.filter(user=db_user, name=AlertDetectionType.NEW_COUNTRY.value).count()) + self.assertEqual(6, Alert.objects.filter(user=db_user, name=AlertDetectionType.IMP_TRAVEL.value).count()) self.assertEqual(5, Alert.objects.filter(user=db_user, is_vip=True).count()) self.assertEqual(11, Alert.objects.filter(user=db_user).count()) diff --git a/buffalogs/impossible_travel/tests/test_views.py b/buffalogs/impossible_travel/tests/test_views.py index bc09c0c..6eea8df 100644 --- a/buffalogs/impossible_travel/tests/test_views.py +++ b/buffalogs/impossible_travel/tests/test_views.py @@ -3,6 +3,7 @@ from django.test import Client from django.urls import reverse +from impossible_travel.constants import AlertDetectionType, UserRiskScoreType from impossible_travel.models import Alert, Login, User from rest_framework.test import APITestCase @@ -12,11 +13,11 @@ 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), + User(username="Lorena Goldoni", risk_score=UserRiskScoreType.NO_RISK.value), + User(username="Lorygold", risk_score=UserRiskScoreType.LOW.value), + User(username="Lory", risk_score=UserRiskScoreType.LOW.value), + User(username="Lor", risk_score=UserRiskScoreType.LOW.value), + User(username="Loryg", risk_score=UserRiskScoreType.MEDIUM.value), ] ) db_user = User.objects.get(username="Lorena Goldoni") @@ -72,7 +73,7 @@ def setUp(self): [ Alert( user=db_user, - name=Alert.ruleNameEnum.NEW_DEVICE, + name=AlertDetectionType.NEW_DEVICE.value, login_raw_data={ "id": "ht9DEIgBnkLiMp6r-SG-", "ip": "203.0.113.24", @@ -87,7 +88,7 @@ def setUp(self): ), Alert( user=db_user, - name=Alert.ruleNameEnum.IMP_TRAVEL, + name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data={ "id": "vfraw14gw", "ip": "1.2.3.4", @@ -102,7 +103,7 @@ def setUp(self): ), Alert( user=db_user, - name=Alert.ruleNameEnum.IMP_TRAVEL, + name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data={ "id": "vfraw14gw", "ip": "1.2.3.4", @@ -117,7 +118,7 @@ def setUp(self): ), Alert( user=db_user, - name=Alert.ruleNameEnum.IMP_TRAVEL, + name=AlertDetectionType.IMP_TRAVEL.value, login_raw_data={ "id": "vfraw14gw", "ip": "1.2.3.4", @@ -132,7 +133,7 @@ def setUp(self): ), Alert( user=db_user, - name=Alert.ruleNameEnum.NEW_DEVICE, + name=AlertDetectionType.NEW_DEVICE.value, login_raw_data={ "id": "ht9DEIgBnkLiMp6r-SG-", "ip": "203.0.113.24", @@ -147,7 +148,7 @@ def setUp(self): ), Alert( user=db_user, - name=Alert.ruleNameEnum.NEW_DEVICE, + name=AlertDetectionType.NEW_DEVICE.value, login_raw_data={ "id": "ht9DEIgBnkLiMp6r-SG-", "ip": "203.0.113.24", @@ -223,7 +224,7 @@ def test_alerts_api(self): ] response = self.client.get(f"{reverse('alerts_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.assertListEqual(list_expected_result, json.loads(response.content)) + self.assertCountEqual(list_expected_result, json.loads(response.content)) def test_risk_score_api(self): end = datetime.now() + timedelta(seconds=1) diff --git a/buffalogs/requirements.txt b/buffalogs/requirements.txt index 221b0b3..2d27d35 100644 --- a/buffalogs/requirements.txt +++ b/buffalogs/requirements.txt @@ -22,6 +22,7 @@ pygal>=3.0.0 pygal-maps-world>=1.0.2 python-dateutil>=2.8.2 python-dotenv>=0.21.0 +pytz>=2024.1 PyYAML>=6.0 urllib3>=1.26.12 uWSGI>=2.0.21 diff --git a/buffalogs/requirements_dev.txt b/buffalogs/requirements_dev.txt index 8128f2a..4c24ce1 100644 --- a/buffalogs/requirements_dev.txt +++ b/buffalogs/requirements_dev.txt @@ -1,4 +1,6 @@ pre-commit==2.21.0 mypy-extensions>=0.4.3 flake8>=3.8.4 -flake8-django>=1.1.5 \ No newline at end of file +flake8-django>=1.1.5 +ipython>=8.30.0 +pipdeptree>=2.16.0 \ No newline at end of file diff --git a/django-buffalogs/buffalogs.egg-info/PKG-INFO b/django-buffalogs/buffalogs.egg-info/PKG-INFO index cc2a6fe..3af920d 100644 --- a/django-buffalogs/buffalogs.egg-info/PKG-INFO +++ b/django-buffalogs/buffalogs.egg-info/PKG-INFO @@ -1,11 +1,9 @@ Metadata-Version: 2.1 Name: buffalogs -Version: 1.2.11 +Version: 1.3.0 Summary: A Django app to detect anomaly logins. -Home-page: UNKNOWN Author: Lorena Goldoni License: Apache-2.0 -Platform: UNKNOWN Classifier: Framework :: Django Classifier: License :: OSI Approved :: Apache-2.0 Licence Classifier: Operating System :: OS Independent @@ -13,6 +11,36 @@ Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development Requires-Python: >=3.8 License-File: LICENSE.txt +Requires-Dist: celery>=5.2.7 +Requires-Dist: certifi>=2022.9.24 +Requires-Dist: cfgv>=3.3.1 +Requires-Dist: distlib>=0.3.6 +Requires-Dist: Django>=4.1.4 +Requires-Dist: djangorestframework>=3.14.0 +Requires-Dist: djangorestframework-simplejwt>=5.3.0 +Requires-Dist: django-cors-headers>=4.3.0 +Requires-Dist: django-environ>=0.9.0 +Requires-Dist: djangorestframework>=3.14.0 +Requires-Dist: elasticsearch>=7.17.7 +Requires-Dist: elasticsearch-dsl>=7.4.0 +Requires-Dist: filelock>=3.9.0 +Requires-Dist: geographiclib>=2.0 +Requires-Dist: geopy>=2.3.0 +Requires-Dist: kombu>=5.2.4 +Requires-Dist: nodeenv>=1.7.0 +Requires-Dist: pathspec>=0.10.3 +Requires-Dist: prompt-toolkit>=3.0.33 +Requires-Dist: psycopg>=3.1.12 +Requires-Dist: psycopg-binary>=3.1.12 +Requires-Dist: pygal>=3.0.0 +Requires-Dist: pygal-maps-world>=1.0.2 +Requires-Dist: python-dateutil>=2.8.2 +Requires-Dist: python-dotenv>=0.21.0 +Requires-Dist: PyYAML>=6.0 +Requires-Dist: urllib3>=1.26.12 +Requires-Dist: uWSGI>=2.0.21 +Requires-Dist: virtualenv>=20.17.1 +Requires-Dist: wcwidth>=0.2.5 ========= BuffaLogs @@ -56,5 +84,3 @@ After that, the new package contained in the `django-buffalogs/dist` folder can In the other projects, install the app with ``python -m pip install buffalogs-.tar.gz`` command. If you want to uninstall the application, run ``python -m pip uninstall buffalogs``. - - diff --git a/django-buffalogs/buffalogs.egg-info/SOURCES.txt b/django-buffalogs/buffalogs.egg-info/SOURCES.txt index 44a332a..a7853dc 100644 --- a/django-buffalogs/buffalogs.egg-info/SOURCES.txt +++ b/django-buffalogs/buffalogs.egg-info/SOURCES.txt @@ -13,15 +13,13 @@ docs/static/cover_buffalogs.png impossible_travel/__init__.py impossible_travel/admin.py impossible_travel/apps.py +impossible_travel/constants.py impossible_travel/models.py impossible_travel/tasks.py impossible_travel/views.py impossible_travel/management/commands/clear_models.py impossible_travel/management/commands/impossible_travel.py impossible_travel/management/commands/setup_config.py -impossible_travel/management/commands/__pycache__/clear_models.cpython-310.pyc -impossible_travel/management/commands/__pycache__/impossible_travel.cpython-310.pyc -impossible_travel/management/commands/__pycache__/setup_config.cpython-310.pyc impossible_travel/migrations/0001_initial.py impossible_travel/migrations/0002_alert_updated.py impossible_travel/migrations/0003_alter_alert_updated.py @@ -32,10 +30,11 @@ impossible_travel/migrations/0007_login_event_id_login_ip.py impossible_travel/migrations/0008_usersip.py impossible_travel/migrations/0009_config_ignored_ips_config_ignored_users_and_more.py impossible_travel/migrations/0010_config_alert_max_days_config_distance_accepted_and_more.py +impossible_travel/migrations/0011_alert_filter_type_alert_is_filtered_and_more.py impossible_travel/migrations/__init__.py impossible_travel/modules/impossible_travel.py impossible_travel/modules/login_from_new_country.py impossible_travel/modules/login_from_new_device.py -impossible_travel/modules/__pycache__/impossible_travel.cpython-310.pyc -impossible_travel/modules/__pycache__/login_from_new_country.cpython-310.pyc -impossible_travel/modules/__pycache__/login_from_new_device.cpython-310.pyc \ No newline at end of file +impossible_travel/modules/__pycache__/impossible_travel.cpython-312.pyc +impossible_travel/modules/__pycache__/login_from_new_country.cpython-312.pyc +impossible_travel/modules/__pycache__/login_from_new_device.cpython-312.pyc \ No newline at end of file diff --git a/django-buffalogs/buffalogs.egg-info/requires.txt b/django-buffalogs/buffalogs.egg-info/requires.txt index efc151f..4fbf065 100644 --- a/django-buffalogs/buffalogs.egg-info/requires.txt +++ b/django-buffalogs/buffalogs.egg-info/requires.txt @@ -1,16 +1,15 @@ -Django>=4.1.4 -PyYAML>=6.0 celery>=5.2.7 certifi>=2022.9.24 cfgv>=3.3.1 distlib>=0.3.6 +Django>=4.1.4 +djangorestframework>=3.14.0 +djangorestframework-simplejwt>=5.3.0 django-cors-headers>=4.3.0 django-environ>=0.9.0 -djangorestframework-simplejwt>=5.3.0 djangorestframework>=3.14.0 -djangorestframework>=3.14.0 -elasticsearch-dsl>=7.4.0 elasticsearch>=7.17.7 +elasticsearch-dsl>=7.4.0 filelock>=3.9.0 geographiclib>=2.0 geopy>=2.3.0 @@ -18,13 +17,14 @@ kombu>=5.2.4 nodeenv>=1.7.0 pathspec>=0.10.3 prompt-toolkit>=3.0.33 -psycopg-binary>=3.1.12 psycopg>=3.1.12 -pygal-maps-world>=1.0.2 +psycopg-binary>=3.1.12 pygal>=3.0.0 +pygal-maps-world>=1.0.2 python-dateutil>=2.8.2 python-dotenv>=0.21.0 -uWSGI>=2.0.21 +PyYAML>=6.0 urllib3>=1.26.12 +uWSGI>=2.0.21 virtualenv>=20.17.1 wcwidth>=0.2.5 diff --git a/django-buffalogs/setup.cfg b/django-buffalogs/setup.cfg index 0ec3dff..a4e24b7 100644 --- a/django-buffalogs/setup.cfg +++ b/django-buffalogs/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = buffalogs -version = 1.2.11 +version = 1.3.0 description = A Django app to detect anomaly logins. long_description = file: README.rst author = Lorena Goldoni