From 95ed3a2d93413799845eade9069b829566668302 Mon Sep 17 00:00:00 2001 From: sahama Date: Sat, 10 Aug 2024 18:36:33 +0330 Subject: [PATCH] new type for payer and payee --- apps/gooyal_oauth2/rest_framework.py | 57 +++++++++++++++++++++++++++- apps/wallet/constans.py | 5 +++ apps/wallet/models.py | 22 ++++++----- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/apps/gooyal_oauth2/rest_framework.py b/apps/gooyal_oauth2/rest_framework.py index b9fec63..60abc75 100644 --- a/apps/gooyal_oauth2/rest_framework.py +++ b/apps/gooyal_oauth2/rest_framework.py @@ -1,8 +1,9 @@ import logging +from django.core.exceptions import ImproperlyConfigured from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements, OAuth2Authentication from rest_framework.permissions import ( - IsAuthenticated + IsAuthenticated, BasePermission ) log = logging.getLogger("oauth2_provider") @@ -17,3 +18,57 @@ class IsAuthenticatedOrTokenMatchesOASRequirements(TokenMatchesOASRequirements): token_has_scope = TokenMatchesOASRequirements() return (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view) + + +class ActionMatchesOASRequirements(BasePermission): + def has_permission(self, request, view): + token = request.auth + + if not token: + return False + + if hasattr(token, "scope"): # OAuth 2 + # {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy', 'head': 'retrieve'} + + required_action_scopes = self.get_required_action_scopes(request, view) + + action = request.action.upper() + if action in required_action_scopes: + log.debug( + "Required scopes actions to access resource: {0}".format( + required_action_scopes[action] + ) + ) + for alt in required_action_scopes[action]: + if token.is_valid(alt): + return True + return False + else: + log.warning("no scope alternates defined for method {0}".format(m)) + return False + + assert False, ( + "ActionMatchesOASRequirements requires the" + "`oauth2_provider.rest_framework.OAuth2Authentication` authentication " + "class to be used." + ) + + def get_required_action_scopes(self, request, view): + try: + return getattr(view, "required_action_scopes") + except AttributeError: + raise ImproperlyConfigured( + "ActionMatchesOASRequirements requires the view to" + " define the required_action_scopes attribute" + ) + + +class IsAuthenticatedOrActionMatchesOASRequirements(ActionMatchesOASRequirements): + def has_permission(self, request, view): + is_authenticated = IsAuthenticated().has_permission(request, view) + oauth2authenticated = False + if is_authenticated: + oauth2authenticated = isinstance(request.successful_authenticator, OAuth2Authentication) + + action_has_scope = ActionMatchesOASRequirements() + return (is_authenticated and not oauth2authenticated) or action_has_scope.has_permission(request, view) diff --git a/apps/wallet/constans.py b/apps/wallet/constans.py index b35bba6..73d8545 100644 --- a/apps/wallet/constans.py +++ b/apps/wallet/constans.py @@ -10,3 +10,8 @@ class StateChoices(IntegerChoices): SUCCESS = 5 , _('success') FAILED = 6 , _('failed') EXPECTED_FAILURE = 7 , _('expected_failure') # no exact data available but guessed to be failed + + +class TypeChoices(IntegerChoices): + USER = 1 , _('user') + APPLICATION = 2 , _('application') \ No newline at end of file diff --git a/apps/wallet/models.py b/apps/wallet/models.py index c0ee142..97d29c5 100755 --- a/apps/wallet/models.py +++ b/apps/wallet/models.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import APIException, ValidationError from django.conf import settings from oauth2_provider import settings as oauth2_settings -from .constans import StateChoices +from .constans import StateChoices, TypeChoices from apps.users.models import User @@ -54,16 +54,18 @@ class Transaction(models.Model): db_index=True) # reserve_code amount = models.IntegerField(_('amount'), blank=False) delay = models.IntegerField(default=0, blank=True) # number of days for delayed payments - payer = models.ForeignKey(User, on_delete=models.PROTECT, related_name='payments', blank=True, null=True) - payee = models.ForeignKey(User, on_delete=models.PROTECT, related_name='receipts') + payer_id = models.UUIDField(blank=True, null=True) + payer_type = models.IntegerField(choices=TypeChoices.choices, blank=True, null=True) + payee_id = models.UUIDField() + payee_type = models.IntegerField(choices=TypeChoices.choices, blank=True, null=True) state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED) - # create = models.DateTimeField(_('create'), blank=True, null=True, auto_now=True) - created_at = models.DateTimeField(_('created_at'), blank=True, null=True, auto_now=True) - # last_update = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True) - updated_at = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True) - application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, - related_name='transactions', null=True) - detail_id = models.IntegerField(null=True, blank=True) # TODO: or better detail url + created_at = models.DateTimeField(auto_now_add=True, null=True) + updated_at = models.DateTimeField(auto_now=True, null=True) + payer_application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, + related_name='payer_transactions', null=True) + payee_application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, + related_name='payee_transactions', null=True) + detail = models.JSONField(null=True, blank=True, default={}) objects = TransactionManager()