From 9f66406cd660fd845ce0a0b9003cb4374d5eacad Mon Sep 17 00:00:00 2001 From: Horilla Date: Wed, 21 May 2025 11:18:25 +0530 Subject: [PATCH] [IMP] BASE: [UPDT] BASE: Updated the media handling for resricting unautherized use of media files --- base/urls.py | 8 ++- base/views.py | 136 ++++++++++++++++++++++++++++++++++++++++++++++-- horilla/urls.py | 4 +- 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/base/urls.py b/base/urls.py index 3bd7fa13a..000f6bcf0 100644 --- a/base/urls.py +++ b/base/urls.py @@ -1,5 +1,5 @@ from django.contrib.auth.models import Group -from django.urls import path +from django.urls import path, re_path from django.utils.translation import gettext_lazy as _ from base import announcement, request_and_approve, views @@ -93,6 +93,8 @@ urlpatterns = [ path("reset-send-success", views.reset_send_success, name="reset-send-success"), path("change-password", views.change_password, name="change-password"), path("change-username", views.change_username, name="change-username"), + path("two-factor", views.two_factor_auth, name="two-factor"), + path("send-otp", views.send_otp, name="send-otp"), path("logout", views.logout_user, name="logout"), path("settings", views.common_settings, name="settings"), path( @@ -1069,3 +1071,7 @@ urlpatterns = [ ), path("view-penalties", views.view_penalties, name="view-penalties"), ] + +urlpatterns.append( + re_path(r"^media/(?P.*)$", views.protected_media, name="protected_media"), +) diff --git a/base/views.py b/base/views.py index fa00fdcc2..aff4a1dbc 100644 --- a/base/views.py +++ b/base/views.py @@ -7,11 +7,13 @@ This module is used to map url pattens with django views or methods import csv import json import os +import random +import threading import uuid from datetime import datetime, timedelta from email.mime.image import MIMEImage from os import path -from urllib.parse import parse_qs, unquote, urlencode +from urllib.parse import parse_qs, unquote, urlencode, urlparse import pandas as pd from dateutil import parser @@ -23,10 +25,16 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import Group, Permission, User from django.contrib.auth.views import PasswordResetConfirmView, PasswordResetView from django.core.files.base import ContentFile -from django.core.mail import EmailMultiAlternatives +from django.core.mail import EmailMessage, EmailMultiAlternatives, send_mail from django.core.management import call_command from django.db.models import ProtectedError, Q -from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse +from django.http import ( + FileResponse, + Http404, + HttpResponse, + HttpResponseRedirect, + JsonResponse, +) from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse, reverse_lazy @@ -112,6 +120,7 @@ from base.methods import ( filtersubordinatesemployeemodel, format_date, generate_colors, + generate_otp, get_key_instances, is_reportingmanager, paginator_qry, @@ -595,6 +604,7 @@ def login_user(request): return redirect("login") login(request, user) + messages.success(request, _("Login successful.")) # Ensure `next_url` is a safe local URL @@ -794,6 +804,95 @@ def change_username(request): return render(request, "base/auth/username_change.html", {"form": form}) +def two_factor_auth(request): + """ + function to handle two-factor authentication for users. + """ + # request.session["otp_code"] = None + try: + otp = get_otp(request) + except: + otp = None + + if request.method == "POST": + user_otp = request.POST.get("otp") + if user_otp == otp: + request.session["otp_code"] = None + request.session["otp_code_timestamp"] = None + request.session["otp_code_verified"] = True + request.session.save() + messages.success(request, "OTP verified successfully.") + return redirect("/") + elif otp is None: + messages.error(request, "OTP expired. Please request a new one.") + return render(request, "base/auth/two_factor_auth.html") + else: + messages.error(request, "Invalid OTP.") + return render(request, "base/auth/two_factor_auth.html") + + if not horilla_apps.TWO_FACTORS_AUTHENTICATION: + return redirect("/") + + if otp is None: + send_otp(request) + return render(request, "base/auth/two_factor_auth.html") + + +def send_otp(request): + """ + Function to send OTP to the user's email address. + It generates a new OTP code, stores it in the session, and sends it via email. + """ + employee = request.user.employee_get + email = employee.get_mail() + + email_backend = ConfiguredEmailBackend() + display_email_name = email_backend.dynamic_from_email_with_display_name + + otp_code = set_otp(request) + email = EmailMessage( + subject="Your OTP Code", + body=f"Your OTP code is {otp_code}", + from_email=display_email_name, + to=[email], + ) + thread = threading.Thread(target=email.send) + thread.start() + + return redirect("two-factor") + + +def set_otp(request): + """ + Function to set the OTP code in the session. + Generates a new OTP code, stores it in the session, and sets a timestamp for expiration. + """ + + otp_code = generate_otp() + request.session["otp_code"] = otp_code + request.session["otp_code_timestamp"] = timezone.now().timestamp() + request.session["otp_code_verified"] = False + request.session.save() + return otp_code + + +def get_otp(request): + """ + Function to retrieve the OTP code from the session. + Checks if the OTP code has expired (10 minutes) and clears it if so. + """ + created_at = request.session.get("otp_code_timestamp", 0) + current_time = timezone.now().timestamp() + + if current_time - created_at > 600: + request.session["otp_code"] = None + request.session["otp_code_timestamp"] = None + request.session.save() + return None + else: + return request.session.get("otp_code") + + def logout_user(request): """ This method used to logout the user @@ -7388,3 +7487,34 @@ def view_penalties(request): """ records = PenaltyFilter(request.GET).qs return render(request, "penalty/penalty_view.html", {"records": records}) + + +def protected_media(request, path): + page_urls = [ + "/login", + "/forgot-password", + "/change-username", + "/change-password", + "/employee-reset-password", + "/recruitment/candidate-survey", + "/recruitment/open-recruitments", + "/recruitment/candidate-self-status-tracking", + ] + + exempted_folders = ["base/icon/"] + + media_path = os.path.join(settings.MEDIA_ROOT, path) + if not os.path.exists(media_path): + raise Http404("File not found") + + referer = urlparse(request.META.get("HTTP_REFERER", "")) + referer_path = referer.path + + if referer_path not in page_urls and not any( + path.startswith(folder) for folder in exempted_folders + ): + if not request.user.is_authenticated: + messages.error(request, "You must be logged in to access this file.") + return redirect("login") + + return FileResponse(open(media_path, "rb")) diff --git a/horilla/urls.py b/horilla/urls.py index 243430a5f..d9130f2fe 100755 --- a/horilla/urls.py +++ b/horilla/urls.py @@ -44,5 +44,5 @@ urlpatterns = [ path("health/", health_check), ] -if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +# if settings.DEBUG: +# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)