Skip to content

Commit 0a40e55

Browse files
Merge pull request #374 from MySecondLanguage/first-or-last
First or last
2 parents 3dad84d + ed5f912 commit 0a40e55

File tree

11 files changed

+106
-9
lines changed

11 files changed

+106
-9
lines changed

nxtbn/cart/admin_query.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +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
67

78

89
class AdminCartQuery(graphene.ObjectType):
@@ -13,15 +14,18 @@ class AdminCartQuery(graphene.ObjectType):
1314
items_in_cart = graphene.List(CartItemType, cart_id=graphene.ID(required=True))
1415

1516
def resolve_carts(self, info, **kwargs):
17+
check_user_permissions(info, any_staff=True)
1618
return Cart.objects.all()
1719

1820
def resolve_cart_by_user(self, info, user_id):
21+
check_user_permissions(info, any_staff=True)
1922
try:
2023
return Cart.objects.get(user_id=user_id)
2124
except Cart.DoesNotExist:
2225
return None
2326

2427
def resolve_items_in_cart(self, info, cart_id):
28+
check_user_permissions(info, any_staff=True)
2529
try:
2630
cart = Cart.objects.get(id=cart_id)
2731
return cart.items.all()

nxtbn/core/admin_mutation.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
import graphene
33

44
from nxtbn.core import CurrencyTypes
5+
from nxtbn.core.admin_permissions import check_user_permissions
56
from nxtbn.core.admin_types import CurrencyExchangeType
67
from nxtbn.core.models import CurrencyExchange
8+
from nxtbn.users import UserRole
79

810
class CurrencyExchangeInput(graphene.InputObjectType):
911
base_currency = graphene.String(required=True)
@@ -21,6 +23,7 @@ class Arguments:
2123

2224
@staticmethod
2325
def mutate(root, info, input):
26+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
2427
# Validate base_currency
2528
base_currency = input.base_currency
2629
if base_currency != settings.BASE_CURRENCY:
@@ -48,6 +51,7 @@ class Arguments:
4851

4952
@staticmethod
5053
def mutate(root, info, id, input):
54+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
5155
try:
5256
currency_exchange = CurrencyExchange.objects.get(pk=id)
5357
except CurrencyExchange.DoesNotExist:
@@ -65,6 +69,7 @@ class Arguments:
6569

6670
@staticmethod
6771
def mutate(root, info, id):
72+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN])
6873
try:
6974
currency_exchange = CurrencyExchange.objects.get(pk=id)
7075
except CurrencyExchange.DoesNotExist:

nxtbn/core/admin_queries.py

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

55
from nxtbn.core import CurrencyTypes
6+
from nxtbn.core.admin_permissions import check_user_permissions
67
from nxtbn.core.admin_types import AdminCurrencyTypesEnum, CurrencyExchangeType
78
from nxtbn.core.models import CurrencyExchange
89
from graphene_django.filter import DjangoFilterConnectionField
@@ -14,15 +15,18 @@ class AdminCoreQuery(graphene.ObjectType):
1415
allowed_currency_list = graphene.List(AdminCurrencyTypesEnum)
1516

1617
def resolve_currency_exchanges(self, info, **kwargs):
18+
check_user_permissions(info, any_staff=True)
1719
return CurrencyExchange.objects.all()
1820

1921
def resolve_currency_exchange(self, info, id):
22+
check_user_permissions(info, any_staff=True)
2023
try:
2124
return CurrencyExchange.objects.get(id=id)
2225
except CurrencyExchange.DoesNotExist:
2326
return None
2427

2528
def resolve_allowed_currency_list(self, info):
29+
check_user_permissions(info, any_staff=True)
2630
allowed_currency_list = settings.ALLOWED_CURRENCIES
2731

2832
if not allowed_currency_list:

nxtbn/order/admin_queries.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import graphene
22
from graphene_django.filter import DjangoFilterConnectionField
33

4+
from nxtbn.core.admin_permissions import check_user_permissions
45
from nxtbn.order.admin_types import OrderType
56
from nxtbn.order.models import Address, Order
7+
from nxtbn.users import UserRole
68

79

810
class AdminOrderQuery(graphene.ObjectType):
@@ -13,9 +15,11 @@ class AdminOrderQuery(graphene.ObjectType):
1315
# address = graphene.Field(AddressGraphType, id=graphene.Int(required=True))
1416

1517
def resolve_orders(self, info, **kwargs):
18+
check_user_permissions(info, any_staff=True)
1619
return Order.objects.all()
1720

1821
def resolve_order(self, info, id):
22+
check_user_permissions(info, any_staff=True)
1923
try:
2024
order = Order.objects.get(id=id)
2125
except Order.DoesNotExist:
@@ -24,9 +28,11 @@ def resolve_order(self, info, id):
2428
return order
2529

2630
def resolve_addresses(self, info):
31+
check_user_permissions(info, any_staff=True)
2732
return Address.objects.all()
2833

2934
def resolve_address(self, info, id):
35+
check_user_permissions(info, any_staff=True)
3036
try:
3137
address = Address.objects.get(id=id)
3238
except Address.DoesNotExist:

nxtbn/product/admin_mutations.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Arguments:
2020
category = graphene.Field(CategoryType)
2121

2222
def mutate(self, info, id, input):
23-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
23+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER])
2424
category = Category.objects.get(id=id)
2525
category.name = input.name
2626
category.description = input.description
@@ -48,7 +48,7 @@ class Arguments:
4848
product_translation = graphene.Field(ProductTranslationType)
4949

5050
def mutate(self, info, base_product_id, lang_code, name, summary, description, meta_title, meta_description):
51-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER])
51+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
5252
try:
5353
product_translation = ProductTranslation.objects.get(product_id=base_product_id, language_code=lang_code)
5454
except ProductTranslation.DoesNotExist:
@@ -76,7 +76,7 @@ class Arguments:
7676
category_translation = graphene.Field(CategoryTranslationType)
7777

7878
def mutate(self, info, base_category_id, lang_code, name, description, meta_title, meta_description):
79-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
79+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
8080
try:
8181
category_translation = CategoryTranslation.objects.get(category_id=base_category_id, language_code=lang_code)
8282
except CategoryTranslation.DoesNotExist:
@@ -103,7 +103,7 @@ class Arguments:
103103
supplier_translation = graphene.Field(SupplierTranslationType)
104104

105105
def mutate(self, info, base_supplier_id, lang_code, name, description, meta_title, meta_description):
106-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
106+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
107107
try:
108108
supplier_translation = SupplierTranslation.objects.get(supplier_id=base_supplier_id, language_code=lang_code)
109109
except SupplierTranslation.DoesNotExist:
@@ -127,7 +127,7 @@ class Arguments:
127127
product_variant_translation = graphene.Field(ProductVariantTranslationType)
128128

129129
def mutate(self, info, base_product_variant_id, lang_code, name, description, meta_title, meta_description):
130-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
130+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
131131
try:
132132
product_variant_translation = ProductVariantTranslation.objects.get(product_variant_id=base_product_variant_id, language_code=lang_code)
133133
except ProductVariantTranslation.DoesNotExist:
@@ -147,7 +147,7 @@ class Arguments:
147147
product_tag_translation = graphene.Field(ProductTagTranslationType)
148148

149149
def mutate(self, info, base_product_tag_id, lang_code, name, description, meta_title, meta_description):
150-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
150+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
151151
try:
152152
product_tag_translation = ProductTagTranslation.objects.get(product_tag_id=base_product_tag_id, language_code=lang_code)
153153
except ProductTagTranslation.DoesNotExist:
@@ -170,7 +170,7 @@ class Arguments:
170170
collection_translation = graphene.Field(CollectionTranslationType)
171171

172172
def mutate(self, info, base_collection_id, lang_code, name, description, meta_title, meta_description):
173-
check_user_permissions(info, allowed_roles=[UserRole.PRODUCT_MANAGER, UserRole.STORE_MANAGER, UserRole.ADMIN])
173+
check_user_permissions(info, allowed_roles=[UserRole.STORE_MANAGER, UserRole.ADMIN, UserRole.PRODUCT_MANAGER, UserRole.TRANSLATOR])
174174
try:
175175
collection_translation = CollectionTranslation.objects.get(collection_id=base_collection_id, language_code=lang_code)
176176
except CollectionTranslation.DoesNotExist:

nxtbn/product/admin_queries.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ def resolve_product_variants(root, info, **kwargs):
9595
return Product.objects.all()
9696

9797
def resolve_category(root, info, id):
98-
# check_user_permissions(info, any_staff=True)
98+
check_user_permissions(info, any_staff=True)
9999

100100
try:
101101
return Category.objects.get(pk=id)
102102
except Category.DoesNotExist:
103103
return None
104104

105105
def resolve_categories(root, info, **kwargs):
106-
# check_user_permissions(info, any_staff=True)
106+
check_user_permissions(info, any_staff=True)
107107
return Category.objects.all()
108108

109109
# All translations

nxtbn/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,11 @@ def get_env_var(key, default=None, var_type=str):
334334
'SCHEMA': 'nxtbn.admin_schema.admin_schema',
335335
'MIDDLEWARE': [
336336
'graphene_django.debug.DjangoDebugMiddleware',
337+
'nxtbn.users.auth_middleware.MaxQueryDepthMiddleware',
337338
'nxtbn.users.auth_middleware.NXTBNGraphQLAuthenticationMiddleware',
338339
],
339340
'RELAY_CONNECTION_MAX_LIMIT': 100, # pagination limit
341+
'RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST': True,
340342
}
341343

342344

nxtbn/users/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,13 @@ class UserRole(models.TextChoices):
151151
- Restrictions:
152152
- Cannot add, edit, or delete any data.
153153
- Cannot manage users, roles, or system settings.
154+
"""
155+
156+
TRANSLATOR = 'TRANSLATOR', 'Translator'
157+
"""
158+
Translator:
159+
- Description: Responsible for translating content into different languages.
160+
- Permissions:
161+
- Translate product listings, categories, and other site content.
162+
- Manage translations for various languages.
154163
"""

nxtbn/users/admin_mutation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,5 @@ def mutate(self, info, refresh_token):
109109
class AdminUserMutation(graphene.ObjectType):
110110
login = AdminLoginMutation.Field()
111111
refresh_token = AdminTokenRefreshMutation.Field()
112+
113+

nxtbn/users/auth_middleware.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@
22
from django.contrib.auth.models import AnonymousUser
33
from django.contrib.auth import get_user_model
44

5+
6+
from graphql import GraphQLError
7+
from graphql.language.ast import FieldNode, FragmentSpreadNode, InlineFragmentNode
8+
9+
10+
class MaxQueryDepthMiddleware:
11+
def __init__(self, max_depth=6, max_top_level_fields=2):
12+
self.max_depth = max_depth
13+
self.max_top_level_fields = max_top_level_fields
14+
15+
def resolve(self, next, root, info, **kwargs):
16+
operation = info.operation
17+
18+
# Bypass validation for introspection queries
19+
if self._is_introspection_query(operation):
20+
return next(root, info, **kwargs)
21+
22+
# Validate max depth
23+
query_depth = self._calculate_depth(operation.selection_set, info)
24+
if query_depth > self.max_depth:
25+
raise GraphQLError(f"Query depth exceeds the maximum limit of {self.max_depth}. Current depth: {query_depth}.")
26+
27+
# Validate max top-level fields
28+
top_level_fields_count = len(operation.selection_set.selections)
29+
if top_level_fields_count > self.max_top_level_fields:
30+
raise GraphQLError(f"Query exceeds the maximum of {self.max_top_level_fields} top-level fields. Current count: {top_level_fields_count}.")
31+
32+
# Continue to the next middleware or resolver
33+
return next(root, info, **kwargs)
34+
35+
def _is_introspection_query(self, operation):
36+
# Check if the operation contains any introspection fields
37+
if operation.selection_set:
38+
for selection in operation.selection_set.selections:
39+
if isinstance(selection, FieldNode) and selection.name.value.startswith("__"):
40+
return True
41+
return False
42+
43+
def _calculate_depth(self, selection_set, info, depth=0):
44+
if not selection_set or not hasattr(selection_set, "selections"):
45+
return depth
46+
47+
depths = []
48+
for selection in selection_set.selections:
49+
if isinstance(selection, (FieldNode, InlineFragmentNode)):
50+
depths.append(self._calculate_depth(selection.selection_set, info, depth + 1))
51+
elif isinstance(selection, FragmentSpreadNode):
52+
fragment = info.fragments.get(selection.name.value)
53+
if fragment:
54+
depths.append(self._calculate_depth(fragment.selection_set, info, depth + 1))
55+
return max(depths, default=depth)
56+
57+
58+
559
class NXTBNGraphQLAuthenticationMiddleware:
660
def __init__(self):
761
self.jwt_manager = JWTManager()

0 commit comments

Comments
 (0)