Skip to content

Commit cf693e6

Browse files
Merge pull request #376 from MySecondLanguage/handled-entire-permission
Handled entire permission
2 parents 628d83e + d3c5619 commit cf693e6

File tree

31 files changed

+350
-236
lines changed

31 files changed

+350
-236
lines changed

nxtbn/admin_schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from nxtbn.product.admin_queries import ProductQuery
88
from nxtbn.users.admin_mutation import AdminUserMutation
99
from nxtbn.order.admin_queries import AdminOrderQuery
10+
from nxtbn.users.admin_queries import UserAdminQuery
1011
from nxtbn.warehouse.admin_queries import WarehouseQuery
1112

1213

1314

1415

1516

16-
class Query(ProductQuery, AdminOrderQuery, AdminCoreQuery, WarehouseQuery, AdminCartQuery):
17+
class Query(ProductQuery, AdminOrderQuery, AdminCoreQuery, WarehouseQuery, AdminCartQuery, UserAdminQuery):
1718
pass
1819

1920
class Mutation(AdminUserMutation, ProductMutation, CoreMutation):

nxtbn/cart/admin_query.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from nxtbn.cart.models import Cart
55
from nxtbn.cart.admin_types import CartItemType, CartType
6-
from nxtbn.core.admin_permissions import check_user_permissions
6+
from nxtbn.core.admin_permissions import staff_required
77

88

99
class AdminCartQuery(graphene.ObjectType):
@@ -13,19 +13,19 @@ class AdminCartQuery(graphene.ObjectType):
1313

1414
items_in_cart = graphene.List(CartItemType, cart_id=graphene.ID(required=True))
1515

16+
@staff_required
1617
def resolve_carts(self, info, **kwargs):
17-
check_user_permissions(info, any_staff=True)
1818
return Cart.objects.all()
1919

20+
@staff_required
2021
def resolve_cart_by_user(self, info, user_id):
21-
check_user_permissions(info, any_staff=True)
2222
try:
2323
return Cart.objects.get(user_id=user_id)
2424
except Cart.DoesNotExist:
2525
return None
2626

27+
@staff_required
2728
def resolve_items_in_cart(self, info, cart_id):
28-
check_user_permissions(info, any_staff=True)
2929
try:
3030
cart = Cart.objects.get(id=cart_id)
3131
return cart.items.all()

nxtbn/core/admin_mutation.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import graphene
33

44
from nxtbn.core import CurrencyTypes
5-
from nxtbn.core.admin_permissions import check_user_permissions
65
from nxtbn.core.admin_types import CurrencyExchangeType
76
from nxtbn.core.models import CurrencyExchange
87
from nxtbn.users import UserRole
@@ -23,7 +22,6 @@ class Arguments:
2322

2423
@staticmethod
2524
def mutate(root, info, input):
26-
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
2725
# Validate base_currency
2826
base_currency = input.base_currency
2927
if base_currency != settings.BASE_CURRENCY:
@@ -51,7 +49,6 @@ class Arguments:
5149

5250
@staticmethod
5351
def mutate(root, info, id, input):
54-
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
5552
try:
5653
currency_exchange = CurrencyExchange.objects.get(pk=id)
5754
except CurrencyExchange.DoesNotExist:
@@ -69,7 +66,6 @@ class Arguments:
6966

7067
@staticmethod
7168
def mutate(root, info, id):
72-
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
7369
try:
7470
currency_exchange = CurrencyExchange.objects.get(pk=id)
7571
except CurrencyExchange.DoesNotExist:

nxtbn/core/admin_permissions.py

Lines changed: 85 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,109 @@
33

44
from nxtbn.users import UserRole
55

6-
class NxtbnAdminPermission(BasePermission):
6+
7+
import functools
8+
from graphql import GraphQLError
9+
class GranularPermission(BasePermission):
10+
def get_permission_name(self, model_name, action):
11+
12+
return f"{model_name}.{action}"
13+
714
def has_permission(self, request, view):
8-
user = request.user
9-
if not user.is_authenticated:
15+
if not request.user.is_authenticated:
1016
return False
1117

12-
if user.is_superuser:
18+
if request.user.is_superuser:
19+
return True
20+
21+
if request.user.method in SAFE_METHODS and request.user.is_staff: # Every staff can view
1322
return True
23+
24+
model_name = view.queryset.model.__name__.lower() # Get model name dynamically
25+
action = view.action.required_perm
1426

15-
return False
27+
permission_name = self.get_permission_name(model_name, action)
1628

29+
# Check if the user has the generated permission
30+
return request.user.has_perm(permission_name)
31+
1732

18-
class RoleBasedPermission(BasePermission):
19-
"""
20-
Custom permission that grants or denies access based on the role and action.
21-
"""
22-
def has_permission(self, request, view):
23-
user = request.user
33+
class ModelPermissions(BasePermission):
2434

25-
# Ensure user is authenticated
26-
if not user.is_authenticated:
35+
def has_permission(self, request, view):
36+
if not request.user.is_authenticated:
2737
return False
2838

29-
if user.role == UserRole.ADMIN:
39+
if request.user.is_superuser:
40+
return True
41+
42+
if request.user.method in SAFE_METHODS and request.user.is_staff: # Every staff can view
3043
return True
3144

3245

33-
action = getattr(view, 'role_action', None) or getattr(view, 'action', None)
3446

47+
model_cls = getattr(view, 'queryset', None)
48+
if model_cls is None:
49+
return False
3550

51+
model_meta = model_cls.model._meta
3652

37-
# Check if action exists in the permissions for the user's role
38-
role_permissions = view.ROLE_PERMISSIONS.get(user.role, set())
53+
method_permissions_map = {
54+
'GET': f'{model_meta.app_label}.view_{model_meta.model_name}',
55+
'OPTIONS': f'{model_meta.app_label}.view_{model_meta.model_name}',
56+
'HEAD': f'{model_meta.app_label}.view_{model_meta.model_name}',
57+
'POST': f'{model_meta.app_label}.add_{model_meta.model_name}',
58+
'PUT': f'{model_meta.app_label}.change_{model_meta.model_name}',
59+
'PATCH': f'{model_meta.app_label}.change_{model_meta.model_name}',
60+
'DELETE': f'{model_meta.app_label}.delete_{model_meta.model_name}',
61+
}
3962

40-
if "all" in role_permissions:
41-
return True
63+
required_permission = method_permissions_map.get(request.method)
4264

43-
# Grant permission if the action is allowed for the user's role
44-
if action in role_permissions:
45-
return True
65+
if required_permission is None:
66+
return False
4667

47-
return False
48-
68+
return request.user.has_perm(required_permission)
4969

50-
def check_user_permissions(info, any_staff=False, allowed_roles=[]):
51-
if not info.context.user.is_authenticated:
52-
raise Exception("You must be logged in to perform this action")
53-
54-
if not info.context.user.is_staff:
55-
raise Exception("You must be a staff to perform this action")
56-
57-
if info.context.user.is_superuser:
58-
return True
59-
60-
if any_staff:
61-
return True
62-
63-
if info.context.user.role not in allowed_roles:
64-
raise Exception("You do not have permission to perform this action")
70+
71+
72+
73+
def required_perm(code: str): # Used in graphql only
74+
def decorator(func):
75+
@functools.wraps(func)
76+
def wrapper(self, info, *args, **kwargs):
77+
operation = info.operation.operation
78+
user = info.context.user
79+
80+
if user.is_anonymous:
81+
raise GraphQLError("Authentication required")
82+
83+
if operation == "query":
84+
return func(self, info, *args, **kwargs)
85+
86+
87+
88+
if not user.has_perm(code): # Check if user has the required permission
89+
raise GraphQLError("Permission denied") # Block unauthorized access
90+
91+
return func(self, info, *args, **kwargs) # Call the actual resolver
92+
93+
return wrapper
6594

66-
return True
95+
return decorator
96+
97+
98+
def staff_required(func):
99+
@functools.wraps(func)
100+
def wrapper(self, info, *args, **kwargs):
101+
user = info.context.user
102+
103+
if user.is_anonymous:
104+
raise GraphQLError("Authentication required")
105+
106+
if not user.is_staff: # Check if the user is a staff member
107+
raise GraphQLError("Permission denied") # Block access if the user is not staff
108+
109+
return func(self, info, *args, **kwargs) # Call the actual resolver
110+
111+
return wrapper

nxtbn/core/admin_queries.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from graphql import GraphQLError
44

55
from nxtbn.core import CurrencyTypes
6-
from nxtbn.core.admin_permissions import check_user_permissions
6+
from nxtbn.core.admin_permissions import staff_required
77
from nxtbn.core.admin_types import AdminCurrencyTypesEnum, CurrencyExchangeType
88
from nxtbn.core.models import CurrencyExchange
99
from graphene_django.filter import DjangoFilterConnectionField
@@ -14,19 +14,18 @@ class AdminCoreQuery(graphene.ObjectType):
1414
currency_exchange = graphene.Field(CurrencyExchangeType, id=graphene.ID(required=True))
1515
allowed_currency_list = graphene.List(AdminCurrencyTypesEnum)
1616

17+
@staff_required
1718
def resolve_currency_exchanges(self, info, **kwargs):
18-
check_user_permissions(info, any_staff=True)
1919
return CurrencyExchange.objects.all()
2020

2121
def resolve_currency_exchange(self, info, id):
22-
check_user_permissions(info, any_staff=True)
2322
try:
2423
return CurrencyExchange.objects.get(id=id)
2524
except CurrencyExchange.DoesNotExist:
2625
return None
2726

27+
@staff_required
2828
def resolve_allowed_currency_list(self, info):
29-
check_user_permissions(info, any_staff=True)
3029
allowed_currency_list = settings.ALLOWED_CURRENCIES
3130

3231
if not allowed_currency_list:

nxtbn/core/api/dashboard/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
from nxtbn.core import LanguageChoices
2929
from nxtbn.core.api.dashboard.serializers import InvoiceSettingsSerializer, SiteSettingsSerializer
3030
from nxtbn.core.models import InvoiceSettings, SiteSettings
31+
from nxtbn.users import UserRole
3132

3233

3334

3435
class SiteSettingsView(generics.RetrieveUpdateAPIView):
3536
queryset = SiteSettings.objects.all()
3637
serializer_class = SiteSettingsSerializer
3738

39+
3840
def get_object(self):
3941
# Get the current site
4042
current_site = get_current_site(self.request)
@@ -50,6 +52,8 @@ class InvoiceSettingsView(generics.RetrieveUpdateAPIView):
5052
queryset = InvoiceSettings.objects.all()
5153
serializer_class = InvoiceSettingsSerializer
5254

55+
56+
5357
def get_object(self):
5458
current_site = get_current_site(self.request)
5559
try:

nxtbn/core/enum_perms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
from django.db import models
3+
4+
class PermissionsEnum(models.TextChoices):
5+
CAN_APPROVE_ORDER = "can_approve_order"
6+
CAN_CANCEL_ORDER = "can_cancel_order"
7+
CAN_SHIP_ORDER = "can_ship_order"

nxtbn/discount/api/dashboard/views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import django_filters
1010
from django_filters import rest_framework as filters
1111

12+
from nxtbn.users import UserRole
13+
1214

1315
class PromocodeFilter(filters.FilterSet):
1416
username = filters.CharFilter(field_name='username', lookup_expr='icontains')
@@ -58,6 +60,7 @@ class PromoCodeUpdateRetrieveDeleteView(generics.RetrieveUpdateDestroyAPIView):
5860
serializer_class = PromoCodeCountedSerializer
5961
lookup_field = 'id'
6062

63+
6164
class AttachPromoCodeEntitiesAPIView(generics.CreateAPIView):
6265
serializer_class = AttachPromoCodeEntitiesSerializer
6366

@@ -111,4 +114,3 @@ class PromoCodeUsageListAPIView(generics.ListAPIView):
111114
queryset = PromoCodeUsage.objects.all()
112115
serializer_class = PromoCodeUsageSerializer
113116
pagination_class = NxtbnPagination
114-

nxtbn/filemanager/api/dashboard/views.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
DocumentSerializer,
1111
ImageSerializer,
1212
)
13-
from nxtbn.core.admin_permissions import NxtbnAdminPermission
1413
from nxtbn.core.paginator import NxtbnPagination
1514

1615
class ImageFilter(django_filters.FilterSet):
@@ -25,7 +24,6 @@ class ImageListView(generics.ListCreateAPIView):
2524
serializer_class = ImageSerializer
2625
queryset = Image.objects.all().order_by('-created_at')
2726
pagination_class = NxtbnPagination
28-
permission_classes = (NxtbnAdminPermission,)
2927
filter_backends = [ DjangoFilterBackend,]
3028
filterset_class = ImageFilter
3129

@@ -34,20 +32,17 @@ class ImageDetailView(generics.RetrieveUpdateDestroyAPIView):
3432
queryset = Image.objects.all()
3533
serializer_class = ImageSerializer
3634
pagination_class = NxtbnPagination
37-
permission_classes = (NxtbnAdminPermission,)
3835
lookup_field = "id"
3936

4037

4138
class DocumentListView(generics.ListCreateAPIView):
4239
serializer_class = DocumentSerializer
4340
queryset = Document.objects.all()
44-
permission_classes = (NxtbnAdminPermission,)
4541
pagination_class = NxtbnPagination
4642

4743

4844
class DocumentDetailView(generics.RetrieveUpdateDestroyAPIView):
4945
queryset = Document.objects.all()
5046
serializer_class = DocumentSerializer
5147
pagination_class = NxtbnPagination
52-
permission_classes = (NxtbnAdminPermission,)
5348
lookup_field = "id"

nxtbn/invoice/api/dashboard/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from nxtbn.invoice.api.dashboard.serializers import OrderInvoiceSerializer
88
from nxtbn.order.models import Order
9+
from nxtbn.users import UserRole
910

1011

1112
class OrderInvoiceAPIView(generics.RetrieveAPIView):

0 commit comments

Comments
 (0)