From 2ccf3d9679ecc53afba157c917ce86be5fd6af3f Mon Sep 17 00:00:00 2001 From: realaravinth Date: Sat, 18 Jun 2022 21:52:18 +0530 Subject: [PATCH] feat: confirm access decorator DESCRIPTION Some views are privileged and unauthorized execution can have irreversible changes. confirm_access decorator checks if the user's session is verified for privileged operation execution. If not, it will redirect user to "accounts.sudo" vie" vie" vie" view --- accounts/decorators.py | 24 ++++++++++++++++++++++ accounts/utils.py | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 accounts/decorators.py diff --git a/accounts/decorators.py b/accounts/decorators.py new file mode 100644 index 0000000..3f46c84 --- /dev/null +++ b/accounts/decorators.py @@ -0,0 +1,24 @@ +# Copyright © 2022 Aravinth Manivannan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from .utils import ConfirmAccess + + +def confirm_access(function): + def wrap(request, *args, **kwargs): + return ConfirmAccess.validate_decorator( + request=request, fn=function, *args, **kwargs + ) + + return wrap diff --git a/accounts/utils.py b/accounts/utils.py index ba6e81e..37b981f 100644 --- a/accounts/utils.py +++ b/accounts/utils.py @@ -12,8 +12,14 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from datetime import datetime, timezone + from django.utils.crypto import get_random_string from django.core.mail import send_mail +from django.shortcuts import redirect +from django.urls import reverse +from django.utils.http import urlencode +from django.conf import settings def gen_secret() -> str: @@ -36,3 +42,43 @@ def send_verification_email(request, challenge): from_email="No reply Hostea", # TODO read from settings.py recipient_list=[email], ) + + +EPOCH = datetime.utcfromtimestamp(0).astimezone(tz=timezone.utc) + + +def since_epoch(date: datetime = None) -> int: + """Get current time since Unix epoch in seconds""" + if not date: + date = datetime.now(timezone.utc) + date.astimezone(tz=timezone.utc) + return int((date - EPOCH).total_seconds()) + + +class ConfirmAccess: + key = "confirm_access" + + @staticmethod + def redirect_to_sudo(request): + ctx = {"next": request.path} + return redirect(f"{reverse('accounts.sudo')}?{urlencode(ctx)}") + + @classmethod + def is_valid(cls, request): + if cls.key in request.session: + if (since_epoch() - request.session[cls.key]) < settings.HOSTEA["ACCOUNTS"][ + "SUDO_TTL" + ]: + return True + return False + + @classmethod + def validate_decorator(cls, request, fn, *args, **kwargs): + if cls.is_valid(request): + return fn(request, *args, **kwargs) + else: + return cls.redirect_to_sudo(request) + + @classmethod + def set(cls, request): + request.session[cls.key] = since_epoch()