diff --git a/.github/workflows/build_push_dev.yml b/.github/workflows/build_push_dev.yml index 5266965f0..8c4f35a7d 100644 --- a/.github/workflows/build_push_dev.yml +++ b/.github/workflows/build_push_dev.yml @@ -10,13 +10,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up QEMU uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8026d2bc3645ea78b0d2544766a1225eb5691f89 # v3.7.0 + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Login to Docker Hub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -52,7 +52,7 @@ jobs: VERSION=dev - name: Run SCA vulnerability scanners - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_sca_dev.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} diff --git a/.github/workflows/build_push_release.yml b/.github/workflows/build_push_release.yml index 8d39c6c3d..e076b1c74 100644 --- a/.github/workflows/build_push_release.yml +++ b/.github/workflows/build_push_release.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: 'v${{ github.event.inputs.release }}' - @@ -24,7 +24,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8026d2bc3645ea78b0d2544766a1225eb5691f89 # v3.7.0 + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Login to Docker Hub uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -64,13 +64,13 @@ jobs: VERSION=${{ github.event.inputs.release }} - name: Run vulnerability scanners for images - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_sca_current.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} - name: Run vulnerability scanners for endpoints - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_endpoints.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} diff --git a/.github/workflows/check_backend.yml b/.github/workflows/check_backend.yml index cc8ebfa32..8286c74e8 100644 --- a/.github/workflows/check_backend.yml +++ b/.github/workflows/check_backend.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Python 3.12 uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: diff --git a/.github/workflows/check_frontend.yml b/.github/workflows/check_frontend.yml index ed41d733e..b61dbe496 100644 --- a/.github/workflows/check_frontend.yml +++ b/.github/workflows/check_frontend.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version: 20 @@ -30,16 +30,11 @@ jobs: run: | npx eslint src - - name: Check build - working-directory: ./frontend + - name: End-to-end tests + working-directory: . run: | - npm run build - - # - name: End-to-end tests - # working-directory: . - # run: | - # cd end_to_end_tests - # npm install - # cd .. - # docker compose -f docker-compose-playwright.yml build - # docker compose -f docker-compose-playwright.yml up --abort-on-container-exit --exit-code-from playwright + cd end_to_end_tests + npm install + cd .. + docker compose -f docker-compose-playwright.yml build + docker compose -f docker-compose-playwright.yml up --abort-on-container-exit --exit-code-from playwright diff --git a/.github/workflows/check_vulnerabilities.yml b/.github/workflows/check_vulnerabilities.yml index 83516134b..8d0c5d0d7 100644 --- a/.github/workflows/check_vulnerabilities.yml +++ b/.github/workflows/check_vulnerabilities.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Run vulnerability scanners for code - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_code.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} diff --git a/.github/workflows/generate_sboms.yml b/.github/workflows/generate_sboms.yml index f0da7adc6..575803d4c 100644 --- a/.github/workflows/generate_sboms.yml +++ b/.github/workflows/generate_sboms.yml @@ -21,7 +21,7 @@ jobs: node-version: 20 - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: 'v${{ github.event.inputs.release }}' - diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index e0dd7c93c..59c400633 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - chore/deploy-docs + - chore/doc_trivy_secrets permissions: read-all @@ -14,11 +14,11 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: 3.x - - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + - uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 with: key: ${{ github.ref }} path: .cache diff --git a/.github/workflows/scan_sca_current.yml b/.github/workflows/scan_sca_current.yml index 862e403ee..28fb28b40 100644 --- a/.github/workflows/scan_sca_current.yml +++ b/.github/workflows/scan_sca_current.yml @@ -13,18 +13,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: - ref: 'v1.19.0' + ref: 'v1.20.0' - name: Run SCA vulnerability scanners - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_sca_current.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} - name: Run endpoint vulnerability scanners - uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@cca1b2fcc133cf278996436bc61db3ac5031c9fc # main + uses: MaibornWolff/secobserve_actions_templates/actions/vulnerability_scanner@70451f5f17e1de8fddcaebb767167a279dc8815b # main with: so_configuration: 'so_configuration_endpoints.yml' SO_API_TOKEN: ${{ secrets.SO_API_TOKEN }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a2c97db67..296b5434c 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: persist-credentials: false @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11 + uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 with: sarif_file: results.sarif diff --git a/backend/application/__init__.py b/backend/application/__init__.py index 4374f0159..0107b31aa 100644 --- a/backend/application/__init__.py +++ b/backend/application/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.19.0" +__version__ = "1.20.0" import pymysql diff --git a/backend/application/access_control/api/filters.py b/backend/application/access_control/api/filters.py index 468d5cf87..105ed95b1 100644 --- a/backend/application/access_control/api/filters.py +++ b/backend/application/access_control/api/filters.py @@ -1,6 +1,12 @@ +from django.db.models import Exists from django_filters import CharFilter, FilterSet, NumberFilter, OrderingFilter -from application.access_control.models import API_Token, Authorization_Group, User +from application.access_control.models import ( + API_Token, + Authorization_Group, + Authorization_Group_Member, + User, +) class UserFilter(FilterSet): @@ -53,6 +59,7 @@ def __init__(self, data=None, queryset=None, *, request=None, prefix=None): class AuthorizationGroupFilter(FilterSet): name = CharFilter(field_name="name", lookup_expr="icontains") oidc_group = CharFilter(field_name="oidc_group", lookup_expr="icontains") + user = NumberFilter(field_name="users") # search is needed for the ReferenceArrayInput field of react-admin search = CharFilter(field_name="name", lookup_expr="icontains") @@ -66,6 +73,36 @@ class Meta: model = Authorization_Group fields = ["name", "oidc_group", "search"] + def get_user(self, queryset, name, value): # pylint: disable=unused-argument + # field_name is used as a positional argument + + authorization_group_members = Authorization_Group_Member.objects.filter( + user__id=value + ) + queryset = queryset.annotate( + member=Exists(authorization_group_members), + ) + return queryset.filter(member=True) + + +class AuthorizationGroupMemberFilter(FilterSet): + username = CharFilter(field_name="user__username", lookup_expr="icontains") + full_name = CharFilter(field_name="user__full_name", lookup_expr="icontains") + + ordering = OrderingFilter( + # tuple-mapping retains order + fields=( + ("user__full_name", "user_data.full_name"), + ("authorization_group", "authorization_group"), + ("user", "user"), + ("is_manager", "is_manager"), + ), + ) + + class Meta: + model = Authorization_Group_Member + fields = ["authorization_group", "user", "is_manager", "username", "full_name"] + class ApiTokenFilter(FilterSet): name = CharFilter(field_name="user__username", lookup_expr="icontains") diff --git a/backend/application/access_control/api/permissions.py b/backend/application/access_control/api/permissions.py index cda535394..580a9ae93 100644 --- a/backend/application/access_control/api/permissions.py +++ b/backend/application/access_control/api/permissions.py @@ -1,5 +1,11 @@ +from django.shortcuts import get_object_or_404 from rest_framework.permissions import BasePermission +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, +) + class UserHasSuperuserPermission(BasePermission): def has_permission(self, request, view): @@ -20,3 +26,48 @@ def has_object_permission(self, request, view, obj): return request.user.is_superuser return True + + +class UserHasAuthorizationGroupPermission(BasePermission): + def has_permission(self, request, view): + if request.method == "POST": + return not request.user.is_external + + return True + + def has_object_permission(self, request, view, obj: Authorization_Group): + if request.method != "GET": + return _has_manage_permission(request, obj) + + return True + + +class UserHasAuthorizationGroupMemberPermission(BasePermission): + def has_permission(self, request, view): + if request.method == "POST": + authorization_group = get_object_or_404( + Authorization_Group, pk=request.data.get("authorization_group") + ) + return _has_manage_permission(request, authorization_group) + + return True + + def has_object_permission(self, request, view, obj: Authorization_Group_Member): + if request.method != "GET": + return _has_manage_permission(request, obj.authorization_group) + + return True + + +def _has_manage_permission(request, authorization_group: Authorization_Group) -> bool: + user = request.user + if user and user.is_superuser: + return True + + try: + authorization_group_member = Authorization_Group_Member.objects.get( + authorization_group=authorization_group, user=user + ) + return authorization_group_member.is_manager + except Authorization_Group_Member.DoesNotExist: + return False diff --git a/backend/application/access_control/api/serializers.py b/backend/application/access_control/api/serializers.py index 819884917..43d7bebb0 100644 --- a/backend/application/access_control/api/serializers.py +++ b/backend/application/access_control/api/serializers.py @@ -8,13 +8,22 @@ ModelSerializer, Serializer, SerializerMethodField, + ValidationError, ) -from application.access_control.models import API_Token, Authorization_Group, User +from application.access_control.models import ( + API_Token, + Authorization_Group, + Authorization_Group_Member, + User, +) +from application.access_control.queries.authorization_group_member import ( + get_authorization_group_member, +) from application.access_control.services.authorization import get_user_permissions from application.access_control.services.roles_permissions import Permissions, Roles from application.commons.services.global_request import get_current_user -from application.core.models import Product_Member +from application.core.models import Product_Authorization_Group_Member, Product_Member class NestedAuthorizationGroupSerializer(ModelSerializer): @@ -24,6 +33,7 @@ class Meta: class UserListSerializer(ModelSerializer): + full_name = SerializerMethodField() permissions = SerializerMethodField() has_password = SerializerMethodField() @@ -71,6 +81,12 @@ def to_representation(self, instance: User): return data + def get_full_name(self, obj: User) -> str: + if not obj.is_active: + return f"{obj.full_name} (inactive)" + + return obj.full_name + def get_permissions(self, obj: User) -> list[Permissions]: return get_user_permissions(obj) @@ -84,7 +100,10 @@ def get_has_password(self, obj: User) -> bool: class UserSerializer(UserListSerializer): - authorization_groups = NestedAuthorizationGroupSerializer(many=True) + full_name = SerializerMethodField() + has_authorization_groups = SerializerMethodField() + has_product_group_members = SerializerMethodField() + has_product_members = SerializerMethodField() class Meta: model = User @@ -106,7 +125,9 @@ class Meta: "is_oidc_user", "date_joined", "has_password", - "authorization_groups", + "has_authorization_groups", + "has_product_group_members", + "has_product_members", ] def to_representation(self, instance: User): @@ -114,10 +135,28 @@ def to_representation(self, instance: User): user = get_current_user() if user and not user.is_superuser and not user.pk == instance.pk: - data.pop("authorization_groups") + data.pop("has_authorization_groups") + data.pop("has_product_group_members") + data.pop("has_product_members") return data + def get_full_name(self, obj: User) -> str: + return obj.full_name + + def get_has_authorization_groups(self, obj: User) -> bool: + return Authorization_Group_Member.objects.filter(user=obj).exists() + + def get_has_product_group_members(self, obj: User) -> bool: + return Product_Member.objects.filter( + user=obj, product__is_product_group=True + ).exists() + + def get_has_product_members(self, obj: User) -> bool: + return Product_Member.objects.filter( + user=obj, product__is_product_group=False + ).exists() + class UserUpdateSerializer(ModelSerializer): class Meta: @@ -146,13 +185,74 @@ class UserPasswortRulesSerializer(Serializer): class AuthorizationGroupSerializer(ModelSerializer): + has_product_group_members = SerializerMethodField() + has_product_members = SerializerMethodField() + is_manager = SerializerMethodField() + class Meta: model = Authorization_Group - fields = "__all__" + exclude = ["users"] + + def get_has_product_group_members(self, obj: Authorization_Group) -> bool: + return Product_Authorization_Group_Member.objects.filter( + authorization_group=obj, product__is_product_group=True + ).exists() + def get_has_product_members(self, obj: Authorization_Group) -> bool: + return Product_Authorization_Group_Member.objects.filter( + authorization_group=obj, product__is_product_group=False + ).exists() -class AuthorizationGroupUserSerializer(Serializer): - user = IntegerField(validators=[MinValueValidator(0)]) + def get_is_manager(self, obj: Authorization_Group) -> bool: + user = get_current_user() + return Authorization_Group_Member.objects.filter( + authorization_group=obj, user=user, is_manager=True + ).exists() + + +class AuthorizationGroupListSerializer(ModelSerializer): + class Meta: + model = Authorization_Group + exclude = ["users"] + + +class AuthorizationGroupMemberSerializer(ModelSerializer): + authorization_group_data = AuthorizationGroupListSerializer( + source="authorization_group", + read_only=True, + ) + user_data = UserListSerializer(source="user", read_only=True) + + class Meta: + model = Authorization_Group_Member + fields = "__all__" + + def validate(self, attrs: dict): + self.instance: Authorization_Group_Member + data_authorization_group: Optional[Authorization_Group] = attrs.get( + "authorization_group" + ) + data_user = attrs.get("user") + + if self.instance is not None and ( + ( + data_authorization_group + and data_authorization_group != self.instance.authorization_group + ) + or (data_user and data_user != self.instance.user) + ): + raise ValidationError("Authorization group and user cannot be changed") + + if self.instance is None: + authorization_group_member = get_authorization_group_member( + data_authorization_group, data_user + ) + if authorization_group_member: + raise ValidationError( + f"Authorization group member {data_authorization_group} / {data_user} already exists" + ) + + return attrs class UserSettingsSerializer(ModelSerializer): diff --git a/backend/application/access_control/api/views.py b/backend/application/access_control/api/views.py index 961baf61b..6b52f7b02 100644 --- a/backend/application/access_control/api/views.py +++ b/backend/application/access_control/api/views.py @@ -1,8 +1,13 @@ import logging from dataclasses import dataclass +from typing import Any from django.contrib.auth import authenticate as django_authenticate from django.contrib.auth.password_validation import ( + CommonPasswordValidator, + MinimumLengthValidator, + NumericPasswordValidator, + UserAttributeSimilarityValidator, password_validators_help_texts, validate_password, ) @@ -20,15 +25,20 @@ from application.access_control.api.filters import ( ApiTokenFilter, AuthorizationGroupFilter, + AuthorizationGroupMemberFilter, UserFilter, ) -from application.access_control.api.permissions import UserHasSuperuserPermission +from application.access_control.api.permissions import ( + UserHasAuthorizationGroupMemberPermission, + UserHasAuthorizationGroupPermission, + UserHasSuperuserPermission, +) from application.access_control.api.serializers import ( ApiTokenSerializer, AuthenticationRequestSerializer, AuthenticationResponseSerializer, + AuthorizationGroupMemberSerializer, AuthorizationGroupSerializer, - AuthorizationGroupUserSerializer, CreateApiTokenResponseSerializer, ProductApiTokenSerializer, UserListSerializer, @@ -41,15 +51,17 @@ from application.access_control.models import ( API_Token, Authorization_Group, + Authorization_Group_Member, JWT_Secret, User, ) from application.access_control.queries.authorization_group import ( - get_authorization_group_by_id, get_authorization_groups, ) +from application.access_control.queries.authorization_group_member import ( + get_authorization_group_members, +) from application.access_control.queries.user import ( - get_user_by_id, get_users, get_users_without_api_tokens, ) @@ -66,6 +78,7 @@ create_user_api_token, revoke_user_api_token, ) +from application.commons.models import Settings from application.commons.services.log_message import format_log_message from application.core.models import Product from application.core.queries.product import get_product_by_id @@ -169,7 +182,7 @@ def change_password(self, request, pk=None): # pylint: disable=unused-argument raise ValidationError("Current password is incorrect") try: - validate_password(new_password_1, instance) + validate_password(new_password_1, instance, self._get_password_validators()) except DjangoValidationError as e: raise ValidationError(e.messages) # pylint: disable=raise-missing-from # The DjangoValidationError itself is not relevant and must not be re-raised @@ -191,68 +204,49 @@ def password_rules(self, request): class PasswordRules: password_rules: str - password_rules_text = password_validators_help_texts() + password_rules_text = password_validators_help_texts( + self._get_password_validators() + ) password_rules = PasswordRules("- " + "\n- ".join(password_rules_text)) response_serializer = UserPasswortRulesSerializer(password_rules) return Response(response_serializer.data, status=status.HTTP_200_OK) + def _get_password_validators(self) -> list[Any]: + validators: list[Any] = [] + settings = Settings.load() + validators.append( + MinimumLengthValidator( + min_length=settings.password_validator_minimum_length + ) + ) + if settings.password_validator_common_passwords: + validators.append(CommonPasswordValidator()) + if settings.password_validator_attribute_similarity: + validators.append(UserAttributeSimilarityValidator()) + if settings.password_validator_not_numeric: + validators.append(NumericPasswordValidator()) + + return validators + class AuthorizationGroupViewSet(ModelViewSet): serializer_class = AuthorizationGroupSerializer filterset_class = AuthorizationGroupFilter queryset = Authorization_Group.objects.none() - permission_classes = (IsAuthenticated, UserHasSuperuserPermission) + permission_classes = (IsAuthenticated, UserHasAuthorizationGroupPermission) def get_queryset(self): return get_authorization_groups() - @extend_schema( - methods=["POST"], - request=AuthorizationGroupUserSerializer, - responses={status.HTTP_204_NO_CONTENT: None}, - ) - @action(detail=True, methods=["post"]) - def add_user(self, request, pk=None): - request_serializer = AuthorizationGroupUserSerializer(data=request.data) - if not request_serializer.is_valid(): - raise ValidationError(request_serializer.errors) - - user_id = request_serializer.validated_data.get("user") - authorization_group = get_authorization_group_by_id(pk) - if not authorization_group: - raise ValidationError(f"Authorization group {pk} does not exist") - user = get_user_by_id(user_id) - if not user: - raise ValidationError(f"User {user_id} does not exist") +class AuthorizationGroupMemberViewSet(ModelViewSet): + serializer_class = AuthorizationGroupMemberSerializer + filterset_class = AuthorizationGroupMemberFilter + queryset = Authorization_Group_Member.objects.none() + permission_classes = (IsAuthenticated, UserHasAuthorizationGroupMemberPermission) - authorization_group.users.add(user) - - return Response(status=status.HTTP_204_NO_CONTENT) - - @extend_schema( - methods=["POST"], - request=AuthorizationGroupUserSerializer, - responses={status.HTTP_204_NO_CONTENT: None}, - ) - @action(detail=True, methods=["post"]) - def remove_user(self, request, pk=None): - request_serializer = AuthorizationGroupUserSerializer(data=request.data) - if not request_serializer.is_valid(): - raise ValidationError(request_serializer.errors) - - user_id = request_serializer.validated_data.get("user") - - authorization_group = get_authorization_group_by_id(pk) - if not authorization_group: - raise ValidationError(f"Authorization group {pk} does not exist") - user = get_user_by_id(user_id) - if not user: - raise ValidationError(f"User {user_id} does not exist") - - authorization_group.users.remove(user) - - return Response(status=status.HTTP_204_NO_CONTENT) + def get_queryset(self): + return get_authorization_group_members() class ApiTokenViewSet(ListModelMixin, GenericViewSet): diff --git a/backend/application/access_control/migrations/0010_authorization_group_member_and_more.py b/backend/application/access_control/migrations/0010_authorization_group_member_and_more.py new file mode 100644 index 000000000..2fef4170c --- /dev/null +++ b/backend/application/access_control/migrations/0010_authorization_group_member_and_more.py @@ -0,0 +1,72 @@ +# Generated by Django 5.1.2 on 2024-10-10 11:56 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("access_control", "0009_user_is_oidc_user"), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + database_operations=[ + migrations.RunSQL( + sql="ALTER TABLE access_control_authorization_group_users RENAME TO access_control_authorization_group_member", + reverse_sql="ALTER TABLE access_control_authorization_group_member RENAME TO access_control_authorization_group_users", + ), + ], + state_operations=[ + migrations.CreateModel( + name="Authorization_Group_Member", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_manager", models.BooleanField(default=False)), + ( + "authorization_group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="access_control.authorization_group", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("authorization_group", "user")}, + }, + ), + migrations.AlterField( + model_name="authorization_group", + name="users", + field=models.ManyToManyField( + blank=True, + related_name="authorization_groups", + through="access_control.Authorization_Group_Member", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddField( + model_name="authorization_group_member", + name="is_manager", + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/application/access_control/models.py b/backend/application/access_control/models.py index cde118e04..c983fa158 100644 --- a/backend/application/access_control/models.py +++ b/backend/application/access_control/models.py @@ -3,6 +3,7 @@ CASCADE, BooleanField, CharField, + ForeignKey, Index, ManyToManyField, Model, @@ -55,7 +56,12 @@ def save(self, *args, **kwargs): class Authorization_Group(Model): name = CharField(max_length=255, unique=True) oidc_group = CharField(max_length=255, blank=True) - users = ManyToManyField(User, related_name="authorization_groups", blank=True) + users: ManyToManyField = ManyToManyField( + User, + through="Authorization_Group_Member", + related_name="authorization_groups", + blank=True, + ) class Meta: verbose_name = "Authorization Group" @@ -68,6 +74,21 @@ def __str__(self): return self.name +class Authorization_Group_Member(Model): + authorization_group = ForeignKey(Authorization_Group, on_delete=CASCADE) + user = ForeignKey(User, on_delete=CASCADE) + is_manager = BooleanField(default=False) + + class Meta: + unique_together = ( + "authorization_group", + "user", + ) + + def __str__(self): + return f"{self.authorization_group} / {self.user}" + + class JWT_Secret(Model): secret = EncryptedCharField(max_length=255) diff --git a/backend/application/access_control/queries/authorization_group.py b/backend/application/access_control/queries/authorization_group.py index d8066f6f4..b66f1f982 100644 --- a/backend/application/access_control/queries/authorization_group.py +++ b/backend/application/access_control/queries/authorization_group.py @@ -1,18 +1,9 @@ -from typing import Optional - from django.db.models.query import QuerySet from application.access_control.models import Authorization_Group from application.commons.services.global_request import get_current_user -def get_authorization_group_by_id(pk: int) -> Optional[Authorization_Group]: - try: - return Authorization_Group.objects.get(pk=pk) - except Authorization_Group.DoesNotExist: - return None - - def get_authorization_groups() -> QuerySet[Authorization_Group]: user = get_current_user() diff --git a/backend/application/access_control/queries/authorization_group_member.py b/backend/application/access_control/queries/authorization_group_member.py new file mode 100644 index 000000000..e4ee4afed --- /dev/null +++ b/backend/application/access_control/queries/authorization_group_member.py @@ -0,0 +1,41 @@ +from typing import Optional + +from django.db.models.query import QuerySet + +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, + User, +) +from application.access_control.queries.authorization_group import ( + get_authorization_groups, +) +from application.commons.services.global_request import get_current_user + + +def get_authorization_group_member( + authorization_group: Authorization_Group, user: User +) -> Optional[Authorization_Group_Member]: + try: + return Authorization_Group_Member.objects.get( + authorization_group=authorization_group, user=user + ) + except Authorization_Group_Member.DoesNotExist: + return None + + +def get_authorization_group_members() -> QuerySet[Authorization_Group_Member]: + user = get_current_user() + + if user is None: + return Authorization_Group_Member.objects.none() + + authorization_group_members = Authorization_Group_Member.objects.all() + + if user.is_superuser: + return authorization_group_members + + authorization_groups = get_authorization_groups() + return authorization_group_members.filter( + authorization_group__in=authorization_groups + ) diff --git a/backend/application/access_control/signals.py b/backend/application/access_control/signals.py index 8c5e0fe02..d9bef9ffc 100644 --- a/backend/application/access_control/signals.py +++ b/backend/application/access_control/signals.py @@ -8,7 +8,12 @@ from django.db.models.signals import post_delete, post_save from django.dispatch import receiver -from application.access_control.models import Authorization_Group, User +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, + User, +) +from application.commons.services.global_request import get_current_user from application.commons.services.log_message import format_log_message logger = logging.getLogger("secobserve.access_control") @@ -41,15 +46,21 @@ def signal_user_login_failed( # pylint: disable=unused-argument @receiver(post_save, sender=Authorization_Group) -def branch_post_save( # pylint: disable=unused-argument +def authorization_group_post_save( # pylint: disable=unused-argument sender, instance: Authorization_Group, created: bool, **kwargs ) -> None: # sender is needed according to Django documentation _invalidate_oidc_groups_hashes() + if created: + user = get_current_user() + if user and not user.is_superuser: + Authorization_Group_Member.objects.create( + authorization_group=instance, user=user, is_manager=True + ) @receiver(post_delete, sender=Authorization_Group) -def branch_post_delete( # pylint: disable=unused-argument +def authorization_group_post_delete( # pylint: disable=unused-argument sender, instance: Authorization_Group, **kwargs ) -> None: # sender is needed according to Django documentation diff --git a/backend/application/commons/migrations/0009_settings_password_validator_attribute_similarity_and_more.py b/backend/application/commons/migrations/0009_settings_password_validator_attribute_similarity_and_more.py new file mode 100644 index 000000000..b90906d51 --- /dev/null +++ b/backend/application/commons/migrations/0009_settings_password_validator_attribute_similarity_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.1.2 on 2024-10-12 17:38 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "commons", + "0008_remove_settings_background_epss_import_crontab_hours_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="settings", + name="password_validator_attribute_similarity", + field=models.BooleanField( + default=True, + help_text="Validates that the password is sufficiently different from certain attributes of the user.", + ), + ), + migrations.AddField( + model_name="settings", + name="password_validator_common_passwords", + field=models.BooleanField( + default=True, + help_text="Validates that the password is not a common password.", + ), + ), + migrations.AddField( + model_name="settings", + name="password_validator_minimum_length", + field=models.IntegerField( + default=8, + help_text="Validates that the password is of a minimum length.", + validators=[ + django.core.validators.MinValueValidator(1), + django.core.validators.MaxValueValidator(4096), + ], + ), + ), + migrations.AddField( + model_name="settings", + name="password_validator_not_numeric", + field=models.BooleanField( + default=True, + help_text="Validate that the password is not entirely numeric.", + ), + ), + ] diff --git a/backend/application/commons/models.py b/backend/application/commons/models.py index 1e013784c..221e685c8 100644 --- a/backend/application/commons/models.py +++ b/backend/application/commons/models.py @@ -199,6 +199,22 @@ class Settings(Model): help_text="Hour crontab expression for API imports (UTC)", ) + password_validator_minimum_length = IntegerField( + default=8, + validators=[MinValueValidator(1), MaxValueValidator(4096)], + help_text="Validates that the password is of a minimum length.", + ) + password_validator_attribute_similarity = BooleanField( + default=True, + help_text="Validates that the password is sufficiently different from certain attributes of the user.", + ) + password_validator_common_passwords = BooleanField( + default=True, help_text="Validates that the password is not a common password." + ) + password_validator_not_numeric = BooleanField( + default=True, help_text="Validate that the password is not entirely numeric." + ) + def save(self, *args, **kwargs): """ Save object to the database. Removes all other entries if there diff --git a/backend/application/core/api/filters.py b/backend/application/core/api/filters.py index e3d07b3e9..78938a5e9 100644 --- a/backend/application/core/api/filters.py +++ b/backend/application/core/api/filters.py @@ -2,6 +2,7 @@ from django.utils import timezone from django_filters import ( + BooleanFilter, CharFilter, ChoiceFilter, FilterSet, @@ -100,11 +101,13 @@ class Meta: class ProductMemberFilter(FilterSet): product = NumberFilter(field_name="product") + is_product_group = BooleanFilter(field_name="product__is_product_group") ordering = OrderingFilter( # tuple-mapping retains order fields=( ("user__full_name", "user_data.full_name"), + ("product__name", "product_data.name"), ("role", "role"), ), ) @@ -116,11 +119,13 @@ class Meta: class ProductAuthorizationGroupMemberFilter(FilterSet): product = NumberFilter(field_name="product") + is_product_group = BooleanFilter(field_name="product__is_product_group") ordering = OrderingFilter( # tuple-mapping retains order fields=( - ("authorization_group__name", "authorization_group_name"), + ("authorization_group__name", "authorization_group_data.name"), + ("product__name", "product_data.name"), ("role", "role"), ), ) diff --git a/backend/application/core/api/serializers_product.py b/backend/application/core/api/serializers_product.py index 4bb6ecbfb..85f648b13 100644 --- a/backend/application/core/api/serializers_product.py +++ b/backend/application/core/api/serializers_product.py @@ -7,7 +7,10 @@ ValidationError, ) -from application.access_control.api.serializers import UserListSerializer +from application.access_control.api.serializers import ( + NestedAuthorizationGroupSerializer, + UserListSerializer, +) from application.access_control.services.authorization import get_highest_user_role from application.access_control.services.roles_permissions import ( Permissions, @@ -384,6 +387,12 @@ def get_risk_acceptance_expiry_date_calculated( return calculate_risk_acceptance_expiry_date(obj) +class NestedProductSerializerSmall(ModelSerializer): + class Meta: + model = Product + fields = ["id", "name", "is_product_group"] + + class NestedProductListSerializer(ModelSerializer): product_group_name = SerializerMethodField() @@ -399,6 +408,7 @@ def get_product_group_name(self, obj: Product) -> str: class ProductMemberSerializer(ModelSerializer): user_data = UserListSerializer(source="user", read_only=True) + product_data = NestedProductSerializerSmall(source="product", read_only=True) class Meta: model = Product_Member @@ -446,17 +456,15 @@ def validate(self, attrs: dict): class ProductAuthorizationGroupMemberSerializer(ModelSerializer): - authorization_group_name = SerializerMethodField() + authorization_group_data = NestedAuthorizationGroupSerializer( + source="authorization_group", read_only=True + ) + product_data = NestedProductSerializerSmall(source="product", read_only=True) class Meta: model = Product_Authorization_Group_Member fields = "__all__" - def get_authorization_group_name( - self, obj: Product_Authorization_Group_Member - ) -> str: - return obj.authorization_group.name - def validate(self, attrs: dict): self.instance: Product_Authorization_Group_Member data_product: Optional[Product] = attrs.get("product") diff --git a/backend/config/api_router.py b/backend/config/api_router.py index 07a790bdc..737fa35e3 100644 --- a/backend/config/api_router.py +++ b/backend/config/api_router.py @@ -2,6 +2,7 @@ from application.access_control.api.views import ( ApiTokenViewSet, + AuthorizationGroupMemberViewSet, AuthorizationGroupViewSet, ProductApiTokenViewset, UserViewSet, @@ -43,6 +44,11 @@ router.register( "authorization_groups", AuthorizationGroupViewSet, basename="authorization_groups" ) +router.register( + "authorization_group_members", + AuthorizationGroupMemberViewSet, + basename="authorization_group_members", +) router.register("api_tokens", ApiTokenViewSet, basename="api_tokens") router.register( "product_api_tokens", ProductApiTokenViewset, basename="product_api_tokens" diff --git a/backend/poetry.lock b/backend/poetry.lock index 83a37dc9c..1e9cbdc51 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -76,13 +76,13 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "astroid" -version = "3.3.4" +version = "3.3.5" description = "An abstract syntax tree for Python with inference support." optional = true python-versions = ">=3.9.0" files = [ - {file = "astroid-3.3.4-py3-none-any.whl", hash = "sha256:5eba185467253501b62a9f113c263524b4f5d55e1b30456370eed4cdbd6438fd"}, - {file = "astroid-3.3.4.tar.gz", hash = "sha256:e73d0b62dd680a7c07cb2cd0ce3c22570b044dd01bd994bc3a2dd16c6cbba162"}, + {file = "astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8"}, + {file = "astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d"}, ] [package.dependencies] @@ -127,33 +127,33 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "black" -version = "24.8.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -167,7 +167,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -274,101 +274,116 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -398,83 +413,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.1" +version = "7.6.3" description = "Code coverage measurement for Python" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, - {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, - {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, - {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, - {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, - {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, - {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, - {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, - {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, - {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, - {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, - {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, - {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, - {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, - {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6da42bbcec130b188169107ecb6ee7bd7b4c849d24c9370a0c884cf728d8e976"}, + {file = "coverage-7.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c222958f59b0ae091f4535851cbb24eb57fc0baea07ba675af718fb5302dddb2"}, + {file = "coverage-7.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab84a8b698ad5a6c365b08061920138e7a7dd9a04b6feb09ba1bfae68346ce6d"}, + {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70a6756ce66cd6fe8486c775b30889f0dc4cb20c157aa8c35b45fd7868255c5c"}, + {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2e6fa98032fec8282f6b27e3f3986c6e05702828380618776ad794e938f53a"}, + {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:921fbe13492caf6a69528f09d5d7c7d518c8d0e7b9f6701b7719715f29a71e6e"}, + {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6d99198203f0b9cb0b5d1c0393859555bc26b548223a769baf7e321a627ed4fc"}, + {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87cd2e29067ea397a47e352efb13f976eb1b03e18c999270bb50589323294c6e"}, + {file = "coverage-7.6.3-cp310-cp310-win32.whl", hash = "sha256:a3328c3e64ea4ab12b85999eb0779e6139295bbf5485f69d42cf794309e3d007"}, + {file = "coverage-7.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bca4c8abc50d38f9773c1ec80d43f3768df2e8576807d1656016b9d3eeaa96fd"}, + {file = "coverage-7.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c51ef82302386d686feea1c44dbeef744585da16fcf97deea2a8d6c1556f519b"}, + {file = "coverage-7.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca37993206402c6c35dc717f90d4c8f53568a8b80f0bf1a1b2b334f4d488fba"}, + {file = "coverage-7.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c77326300b839c44c3e5a8fe26c15b7e87b2f32dfd2fc9fee1d13604347c9b38"}, + {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e484e479860e00da1f005cd19d1c5d4a813324e5951319ac3f3eefb497cc549"}, + {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c0f4d53ef603397fc894a895b960ecd7d44c727df42a8d500031716d4e8d2"}, + {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37be7b5ea3ff5b7c4a9db16074dc94523b5f10dd1f3b362a827af66a55198175"}, + {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43b32a06c47539fe275106b376658638b418c7cfdfff0e0259fbf877e845f14b"}, + {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77c7bef0724165e795b6b7bf9c4c22a9b8468a6bdb9c6b4281293c6b22a90f"}, + {file = "coverage-7.6.3-cp311-cp311-win32.whl", hash = "sha256:43517e1f6b19f610a93d8227e47790722c8bf7422e46b365e0469fc3d3563d97"}, + {file = "coverage-7.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"}, + {file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"}, + {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"}, + {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"}, + {file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"}, + {file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"}, + {file = "coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6"}, + {file = "coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929"}, + {file = "coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990"}, + {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4"}, + {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39"}, + {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21"}, + {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b"}, + {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4"}, + {file = "coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f"}, + {file = "coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce"}, + {file = "coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3"}, + {file = "coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3"}, + {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d"}, + {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38"}, + {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd"}, + {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92"}, + {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5"}, + {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91"}, + {file = "coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43"}, + {file = "coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0"}, + {file = "coverage-7.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da29ceabe3025a1e5a5aeeb331c5b1af686daab4ff0fb4f83df18b1180ea83e2"}, + {file = "coverage-7.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df8c05a0f574d480947cba11b947dc41b1265d721c3777881da2fb8d3a1ddfba"}, + {file = "coverage-7.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1e3b40b82236d100d259854840555469fad4db64f669ab817279eb95cd535c"}, + {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4adeb878a374126f1e5cf03b87f66279f479e01af0e9a654cf6d1509af46c40"}, + {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43d6a66e33b1455b98fc7312b124296dad97a2e191c80320587234a77b1b736e"}, + {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1990b1f4e2c402beb317840030bb9f1b6a363f86e14e21b4212e618acdfce7f6"}, + {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:12f9515d875859faedb4144fd38694a761cd2a61ef9603bf887b13956d0bbfbb"}, + {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99ded130555c021d99729fabd4ddb91a6f4cc0707df4b1daf912c7850c373b13"}, + {file = "coverage-7.6.3-cp39-cp39-win32.whl", hash = "sha256:c3a79f56dee9136084cf84a6c7c4341427ef36e05ae6415bf7d787c96ff5eaa3"}, + {file = "coverage-7.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:aac7501ae73d4a02f4b7ac8fcb9dc55342ca98ffb9ed9f2dfb8a25d53eda0e4d"}, + {file = "coverage-7.6.3-pp39.pp310-none-any.whl", hash = "sha256:b9853509b4bf57ba7b1f99b9d866c422c9c5248799ab20e652bbb8a184a38181"}, + {file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"}, ] [package.extras] @@ -590,24 +595,24 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = true python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "django" -version = "5.1.1" +version = "5.1.2" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" files = [ - {file = "Django-5.1.1-py3-none-any.whl", hash = "sha256:71603f27dac22a6533fb38d83072eea9ddb4017fead6f67f2562a40402d61c3f"}, - {file = "Django-5.1.1.tar.gz", hash = "sha256:021ffb7fdab3d2d388bc8c7c2434eb9c1f6f4d09e6119010bbb1694dda286bc2"}, + {file = "Django-5.1.2-py3-none-any.whl", hash = "sha256:f11aa87ad8d5617171e3f77e1d5d16f004b79a2cf5d2e1d2b97a6a1f8e9ba5ed"}, + {file = "Django-5.1.2.tar.gz", hash = "sha256:bd7376f90c99f96b643722eee676498706c9fd7dc759f55ebfaf2c08ebcdf4f0"}, ] [package.dependencies] @@ -621,13 +626,13 @@ bcrypt = ["bcrypt"] [[package]] name = "django-cors-headers" -version = "4.4.0" +version = "4.5.0" description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "django_cors_headers-4.4.0-py3-none-any.whl", hash = "sha256:5c6e3b7fe870876a1efdfeb4f433782c3524078fa0dc9e0195f6706ce7a242f6"}, - {file = "django_cors_headers-4.4.0.tar.gz", hash = "sha256:92cf4633e22af67a230a1456cb1b7a02bb213d6536d2dcb2a4a24092ea9cebc2"}, + {file = "django_cors_headers-4.5.0-py3-none-any.whl", hash = "sha256:28c1ded847aa70208798de3e42422a782f427b8b720e8d7319d34b654b5978e6"}, + {file = "django_cors_headers-4.5.0.tar.gz", hash = "sha256:6c01a85cf1ec779a7bde621db853aa3ce5c065a5ba8e27df7a9f9e8dac310f4f"}, ] [package.dependencies] @@ -820,21 +825,21 @@ markdown = ["types-Markdown (>=0.1.5)"] [[package]] name = "dnspython" -version = "2.6.1" +version = "2.7.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] @@ -1229,13 +1234,13 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.12.1" +version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, - {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, ] [package.dependencies] @@ -1243,71 +1248,72 @@ referencing = ">=0.31.0" [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = true -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -1628,13 +1634,13 @@ type = ["mypy (>=1.11.2)"] [[package]] name = "pre-commit" -version = "3.8.0" +version = "4.0.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = true python-versions = ">=3.9" files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, + {file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"}, + {file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"}, ] [package.dependencies] @@ -1942,17 +1948,17 @@ testutils = ["gitpython (>3)"] [[package]] name = "pylint-django" -version = "2.5.5" +version = "2.6.1" description = "A Pylint plugin to help Pylint understand the Django web framework" optional = true -python-versions = ">=3.7,<4.0" +python-versions = "<4.0,>=3.9" files = [ - {file = "pylint_django-2.5.5-py3-none-any.whl", hash = "sha256:5abd5c2228e0e5e2a4cb6d0b4fc1d1cef1e773d0be911412f4dd4fc1a1a440b7"}, - {file = "pylint_django-2.5.5.tar.gz", hash = "sha256:2f339e4bf55776958283395c5139c37700c91bd5ef1d8251ef6ac88b5abbba9b"}, + {file = "pylint-django-2.6.1.tar.gz", hash = "sha256:19e8c85a8573a04e3de7be2ba91e9a7c818ebf05e1b617be2bbae67a906b725f"}, + {file = "pylint_django-2.6.1-py3-none-any.whl", hash = "sha256:359f68fe8c810ee6bc8e1ab4c83c19b15a43b234a24b08978f47a23462b5ce28"}, ] [package.dependencies] -pylint = ">=2.0,<4" +pylint = ">=3.0,<4" pylint-plugin-utils = ">=0.8" [package.extras] @@ -2499,4 +2505,4 @@ unittests = ["coverage", "django-coverage-plugin", "django-extensions"] [metadata] lock-version = "2.0" python-versions = ">= 3.10, < 3.13" -content-hash = "1201736eed488067efeee9deef12f86e40fa46b9ff1cb9e3f5d446c854475f8c" +content-hash = "565acf4ba370c447c86b8c7ce4edd625d64c6b3a1cca37e220bc723094333191" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 27b74f661..a536bbd28 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SecObserve" -version = "1.19.0" +version = "1.20.0" description = "SecObserve is an open source vulnerability management system for software development and cloud environments." license = "BSD-3-Clause" authors = [ @@ -12,7 +12,7 @@ python = ">= 3.10, < 3.13" # Django # ------------------------------------------------------------------------------ gunicorn = "23.0.0" # https://github.com/benoitc/gunicorn -django = "5.1.1" # https://www.djangoproject.com/ +django = "5.1.2" # https://www.djangoproject.com/ django-environ = "0.11.2" # https://github.com/joke2k/django-environ django-filter = "24.3" # https://github.com/carltongibson/django-filter django-csp = "3.8" # https://github.com/mozilla/django-csp @@ -23,7 +23,7 @@ whitenoise = "6.7.0" # https://github.com/evansd/whitenoise # Django REST Framework # ------------------------------------------------------------------------------ djangorestframework = "3.15.2" # https://github.com/encode/django-rest-framework -django-cors-headers = "4.4.0" # https://github.com/adamchainz/django-cors-headers +django-cors-headers = "4.5.0" # https://github.com/adamchainz/django-cors-headers # OpenAPI 3 # ------------------------------------------------------------------------------ drf-spectacular = "0.27.2" # https://github.com/tfranzel/drf-spectacular @@ -69,17 +69,17 @@ django-extensions = { version = "3.2.3", optional = true } # https://github.com # Unittest dependencies # ------------------------------------------------------------------------------ -coverage = { version = "7.6.1", optional = true } # https://github.com/nedbat/coveragepy +coverage = { version = "7.6.3", optional = true } # https://github.com/nedbat/coveragepy django-coverage-plugin = { version = "3.1.0", optional = true } # https://github.com/nedbat/django_coverage_plugin # Code quality dependencies # ------------------------------------------------------------------------------ flake8 = { version = "7.1.1", optional = true } # https://github.com/PyCQA/flake8 flake8-isort = { version = "6.1.1", optional = true } # https://github.com/gforcada/flake8-isort -black = { version = "24.8.0", optional = true } # https://github.com/psf/black +black = { version = "24.10.0", optional = true } # https://github.com/psf/black pylint = { version = "3.3.1", optional = true } # https://github.com/pylint-dev/pylint -pylint-django = { version = "2.5.5", optional = true } # https://github.com/PyCQA/pylint-django -pre-commit = { version = "3.8.0", optional = true } # https://github.com/pre-commit/pre-commit +pylint-django = { version = "2.6.1", optional = true } # https://github.com/PyCQA/pylint-django +pre-commit = { version = "4.0.1", optional = true } # https://github.com/pre-commit/pre-commit mypy = { version = "1.11.2", optional = true } # https://github.com/python/mypy django-stubs = { version = "5.1.0", optional = true } # https://github.com/typeddjango/django-stubs djangorestframework-stubs = { version = "3.15.1", optional = true } # https://github.com/typeddjango/djangorestframework-stubs diff --git a/backend/unittests/access_control/api/test_authorization_authorization_group_members.py b/backend/unittests/access_control/api/test_authorization_authorization_group_members.py new file mode 100644 index 000000000..f3b41d4c3 --- /dev/null +++ b/backend/unittests/access_control/api/test_authorization_authorization_group_members.py @@ -0,0 +1,168 @@ +from unittests.access_control.api.test_authorization import ( + APITest, + TestAuthorizationBase, +) + + +class TestAuthorizationAuthorizationGroupMembers(TestAuthorizationBase): + def test_authorization_authorization_group_members(self): + expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'is_manager': True, 'authorization_group': 3, 'user': 2}, {'id': 2, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 3, 'username': 'db_internal_read', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_read', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:25:06+01:00', 'has_password': False}, 'is_manager': False, 'authorization_group': 3, 'user': 3}]}" + self._test_api( + APITest( + "db_admin", + "get", + "/api/authorization_group_members/", + None, + 200, + expected_data, + ) + ) + + expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'is_manager': True, 'authorization_group': 3, 'user': 2}, {'id': 2, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 3, 'username': 'db_internal_read', 'full_name': 'db_internal_read'}, 'is_manager': False, 'authorization_group': 3, 'user': 3}]}" + self._test_api( + APITest( + "db_internal_write", + "get", + "/api/authorization_group_members/", + None, + 200, + expected_data, + no_second_user=True, + ) + ) + + expected_data = "{'count': 0, 'next': None, 'previous': None, 'results': []}" + self._test_api( + APITest( + "db_product_group_user", + "get", + "/api/authorization_group_members/", + None, + 200, + expected_data, + no_second_user=True, + ) + ) + expected_data = "{'id': 1, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'is_manager': True, 'authorization_group': 3, 'user': 2}" + self._test_api( + APITest( + username="db_internal_write", + method="get", + url="/api/authorization_group_members/1/", + post_data=None, + expected_status_code=200, + expected_data=expected_data, + no_second_user=True, + ) + ) + + expected_data = ( + "{'message': 'No Authorization_Group_Member matches the given query.'}" + ) + self._test_api( + APITest( + "db_product_group_user", + "get", + "/api/authorization_group_members/1/", + None, + 404, + expected_data, + no_second_user=True, + ) + ) + + self._test_api( + APITest( + "db_internal_write", + "get", + "/api/authorization_group_members/99999/", + None, + 404, + expected_data, + no_second_user=True, + ) + ) + + post_data = {"authorization_group": 3, "user": 1, "is_manager": False} + expected_data = ( + "{'message': 'You do not have permission to perform this action.'}" + ) + self._test_api( + APITest( + "db_internal_read", + "post", + "/api/authorization_group_members/", + post_data, + 403, + expected_data, + no_second_user=True, + ) + ) + expected_data = "{'id': 3, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'is_manager': False, 'authorization_group': 3, 'user': 1}" + self._test_api( + APITest( + "db_internal_write", + "post", + "/api/authorization_group_members/", + post_data, + 201, + expected_data, + no_second_user=True, + ) + ) + + post_data = {"is_manager": True} + expected_data = ( + "{'message': 'You do not have permission to perform this action.'}" + ) + self._test_api( + APITest( + "db_internal_read", + "patch", + "/api/authorization_group_members/3/", + post_data, + 403, + expected_data, + no_second_user=True, + ) + ) + + expected_data = "{'id': 3, 'authorization_group_data': {'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'is_manager': True, 'authorization_group': 3, 'user': 1}" + self._test_api( + APITest( + "db_internal_write", + "patch", + "/api/authorization_group_members/3/", + post_data, + 200, + expected_data, + no_second_user=True, + ) + ) + + expected_data = ( + "{'message': 'You do not have permission to perform this action.'}" + ) + self._test_api( + APITest( + "db_internal_read", + "delete", + "/api/authorization_group_members/3/", + None, + 403, + expected_data, + no_second_user=True, + ) + ) + expected_data = "None" + self._test_api( + APITest( + "db_internal_write", + "delete", + "/api/authorization_group_members/3/", + None, + 204, + expected_data, + no_second_user=True, + ) + ) diff --git a/backend/unittests/access_control/api/test_authorization_authorization_groups.py b/backend/unittests/access_control/api/test_authorization_authorization_groups.py index 409491436..c19e32958 100644 --- a/backend/unittests/access_control/api/test_authorization_authorization_groups.py +++ b/backend/unittests/access_control/api/test_authorization_authorization_groups.py @@ -1,3 +1,8 @@ +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, + User, +) from unittests.access_control.api.test_authorization import ( APITest, TestAuthorizationBase, @@ -11,7 +16,7 @@ class TestAuthorizationAuthorizationGroups(TestAuthorizationBase): def test_authorization_authorization_groups(self): prepare_authorization_groups() - expected_data = "{'count': 5, 'next': None, 'previous': None, 'results': [{'id': 4, 'name': 'db_group_internal_write', 'oidc_group': '', 'users': [2]}, {'id': 5, 'name': 'db_group_internal_read', 'oidc_group': '', 'users': [3]}, {'id': 6, 'name': 'db_group_external', 'oidc_group': '', 'users': [4]}, {'id': 7, 'name': 'db_group_product_group', 'oidc_group': '', 'users': [6]}, {'id': 8, 'name': 'db_group_unused', 'oidc_group': '', 'users': []}]}" + expected_data = "{'count': 5, 'next': None, 'previous': None, 'results': [{'id': 4, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': False, 'name': 'db_group_internal_write', 'oidc_group': ''}, {'id': 5, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': False, 'name': 'db_group_internal_read', 'oidc_group': ''}, {'id': 6, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': False, 'name': 'db_group_external', 'oidc_group': ''}, {'id': 7, 'has_product_group_members': True, 'has_product_members': False, 'is_manager': False, 'name': 'db_group_product_group', 'oidc_group': ''}, {'id': 8, 'has_product_group_members': False, 'has_product_members': False, 'is_manager': False, 'name': 'db_group_unused', 'oidc_group': ''}]}" self._test_api( APITest( "db_admin", @@ -23,8 +28,8 @@ def test_authorization_authorization_groups(self): ) ) - expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 4, 'name': 'db_group_internal_write', 'oidc_group': '', 'users': [2]}]}" - expected_data_product_group = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 7, 'name': 'db_group_product_group', 'oidc_group': '', 'users': [6]}]}" + expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 4, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': True, 'name': 'db_group_internal_write', 'oidc_group': ''}]}" + expected_data_product_group = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 7, 'has_product_group_members': True, 'has_product_members': False, 'is_manager': False, 'name': 'db_group_product_group', 'oidc_group': ''}]}" self._test_api( APITest( "db_internal_write", @@ -36,7 +41,7 @@ def test_authorization_authorization_groups(self): expected_data_product_group=expected_data_product_group, ) ) - expected_data = "{'id': 4, 'name': 'db_group_internal_write', 'oidc_group': '', 'users': [2]}" + expected_data = "{'id': 4, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': True, 'name': 'db_group_internal_write', 'oidc_group': ''}" self._test_api( APITest( "db_internal_write", @@ -60,7 +65,7 @@ def test_authorization_authorization_groups(self): ) ) - expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 6, 'name': 'db_group_external', 'oidc_group': '', 'users': [4]}]}" + expected_data = "{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 6, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': False, 'name': 'db_group_external', 'oidc_group': ''}]}" self._test_api( APITest( "db_external", @@ -71,9 +76,7 @@ def test_authorization_authorization_groups(self): expected_data, ) ) - expected_data = ( - "{'id': 6, 'name': 'db_group_external', 'oidc_group': '', 'users': [4]}" - ) + expected_data = "{'id': 6, 'has_product_group_members': False, 'has_product_members': True, 'is_manager': False, 'name': 'db_group_external', 'oidc_group': ''}" self._test_api( APITest( "db_external", @@ -96,7 +99,7 @@ def test_authorization_authorization_groups(self): ) ) - expected_data = "{'id': 9, 'name': 'string', 'oidc_group': 'oidc', 'users': []}" + expected_data = "{'id': 9, 'has_product_group_members': False, 'has_product_members': False, 'is_manager': False, 'name': 'string', 'oidc_group': 'oidc'}" self._test_api( APITest( "db_admin", @@ -112,124 +115,109 @@ def test_authorization_authorization_groups(self): ) ) - expected_data = ( - "{'message': 'You do not have permission to perform this action.'}" - ) + expected_data = "{'id': 10, 'has_product_group_members': False, 'has_product_members': False, 'is_manager': True, 'name': 'string_2', 'oidc_group': 'oidc'}" self._test_api( APITest( "db_internal_write", "post", "/api/authorization_groups/", { - "name": "string", + "name": "string_2", "oidc_group": "oidc", }, - 403, + 201, expected_data, no_second_user=True, ) ) expected_data = ( - "{'id': 9, 'name': 'changed_string', 'oidc_group': 'oidc', 'users': []}" + "{'message': 'You do not have permission to perform this action.'}" ) self._test_api( APITest( - "db_admin", - "patch", - "/api/authorization_groups/9/", - {"name": "changed_string"}, - 200, + "db_external", + "post", + "/api/authorization_groups/", + { + "name": "string_3", + "oidc_group": "oidc", + }, + 403, expected_data, no_second_user=True, ) ) - expected_data = ( - "{'message': 'You do not have permission to perform this action.'}" - ) + expected_data = "{'id': 9, 'has_product_group_members': False, 'has_product_members': False, 'is_manager': False, 'name': 'changed_string', 'oidc_group': 'oidc'}" self._test_api( APITest( - "db_internal_write", + "db_admin", "patch", "/api/authorization_groups/9/", {"name": "changed_string"}, - 403, + 200, expected_data, no_second_user=True, ) ) + expected_data = "{'id': 10, 'has_product_group_members': False, 'has_product_members': False, 'is_manager': True, 'name': 'changed_string_2', 'oidc_group': 'oidc'}" self._test_api( APITest( "db_internal_write", - "delete", - "/api/authorization_groups/9/", - None, - 403, + "patch", + "/api/authorization_groups/10/", + {"name": "changed_string_2"}, + 200, expected_data, no_second_user=True, ) ) - post_data = {"user": 2} - - self._test_api( - APITest( - "db_internal_write", - "post", - "/api/authorization_groups/9/add_user/", - post_data, - 403, - None, - no_second_user=True, - ) + Authorization_Group_Member.objects.create( + authorization_group=Authorization_Group.objects.get(id=10), + user=User.objects.get(username="db_internal_read"), + is_manager=False, ) - self._test_api( - APITest( - "db_admin", - "post", - "/api/authorization_groups/9/add_user/", - post_data, - 204, - None, - no_second_user=True, - ) + expected_data = ( + "{'message': 'You do not have permission to perform this action.'}" ) - self._test_api( APITest( - "db_internal_write", - "post", - "/api/authorization_groups/9/remove_user/", - post_data, + "db_internal_read", + "patch", + "/api/authorization_groups/10/", + {"name": "changed_string_2"}, 403, - None, + expected_data, no_second_user=True, ) ) self._test_api( APITest( - "db_admin", - "post", - "/api/authorization_groups/9/remove_user/", - post_data, - 204, + "db_internal_read", + "delete", + "/api/authorization_groups/10/", None, + 403, + expected_data, no_second_user=True, ) ) + expected_data = None + self._test_api( APITest( - "db_admin", + "db_internal_write", "delete", - "/api/authorization_groups/9/", + "/api/authorization_groups/10/", None, 204, - None, + expected_data, no_second_user=True, ) ) diff --git a/backend/unittests/access_control/api/test_authorization_product_authorization_group_members.py b/backend/unittests/access_control/api/test_authorization_product_authorization_group_members.py index 5b2b3123c..cc9824f54 100644 --- a/backend/unittests/access_control/api/test_authorization_product_authorization_group_members.py +++ b/backend/unittests/access_control/api/test_authorization_product_authorization_group_members.py @@ -11,7 +11,7 @@ class TestAuthorizationProductMembers(TestAuthorizationBase): def test_authorization_product_authorization_group_members(self): prepare_authorization_groups() - expected_data = "{'count': 4, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_name': 'db_group_internal_write', 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_name': 'db_group_internal_read', 'role': 1, 'product': 1, 'authorization_group': 5}, {'id': 3, 'authorization_group_name': 'db_group_external', 'role': 5, 'product': 2, 'authorization_group': 6}, {'id': 4, 'authorization_group_name': 'db_group_product_group', 'role': 5, 'product': 3, 'authorization_group': 7}]}" + expected_data = "{'count': 4, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_data': {'id': 4, 'name': 'db_group_internal_write', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_data': {'id': 5, 'name': 'db_group_internal_read', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'authorization_group': 5}, {'id': 3, 'authorization_group_data': {'id': 6, 'name': 'db_group_external', 'oidc_group': ''}, 'product_data': {'id': 2, 'name': 'db_product_external', 'is_product_group': False}, 'role': 5, 'product': 2, 'authorization_group': 6}, {'id': 4, 'authorization_group_data': {'id': 7, 'name': 'db_group_product_group', 'oidc_group': ''}, 'product_data': {'id': 3, 'name': 'db_product_group', 'is_product_group': True}, 'role': 5, 'product': 3, 'authorization_group': 7}]}" self._test_api( APITest( "db_admin", @@ -23,7 +23,7 @@ def test_authorization_product_authorization_group_members(self): ) ) - expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_name': 'db_group_internal_write', 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_name': 'db_group_internal_read', 'role': 1, 'product': 1, 'authorization_group': 5}]}" + expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_data': {'id': 4, 'name': 'db_group_internal_write', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_data': {'id': 5, 'name': 'db_group_internal_read', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'authorization_group': 5}]}" self._test_api( APITest( "db_internal_write", @@ -36,7 +36,7 @@ def test_authorization_product_authorization_group_members(self): ) ) - expected_data = "{'count': 3, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_name': 'db_group_internal_write', 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_name': 'db_group_internal_read', 'role': 1, 'product': 1, 'authorization_group': 5}, {'id': 4, 'authorization_group_name': 'db_group_product_group', 'role': 5, 'product': 3, 'authorization_group': 7}]}" + expected_data = "{'count': 3, 'next': None, 'previous': None, 'results': [{'id': 1, 'authorization_group_data': {'id': 4, 'name': 'db_group_internal_write', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'authorization_group': 4}, {'id': 2, 'authorization_group_data': {'id': 5, 'name': 'db_group_internal_read', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'authorization_group': 5}, {'id': 4, 'authorization_group_data': {'id': 7, 'name': 'db_group_product_group', 'oidc_group': ''}, 'product_data': {'id': 3, 'name': 'db_product_group', 'is_product_group': True}, 'role': 5, 'product': 3, 'authorization_group': 7}]}" self._test_api( APITest( "db_product_group_user", @@ -47,7 +47,7 @@ def test_authorization_product_authorization_group_members(self): expected_data, ) ) - expected_data = "{'id': 1, 'authorization_group_name': 'db_group_internal_write', 'role': 5, 'product': 1, 'authorization_group': 4}" + expected_data = "{'id': 1, 'authorization_group_data': {'id': 4, 'name': 'db_group_internal_write', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'authorization_group': 4}" self._test_api( APITest( "db_internal_write", @@ -96,7 +96,7 @@ def test_authorization_product_authorization_group_members(self): expected_data, ) ) - expected_data = "{'id': 5, 'authorization_group_name': 'db_group_unused', 'role': 3, 'product': 1, 'authorization_group': 8}" + expected_data = "{'id': 5, 'authorization_group_data': {'id': 8, 'name': 'db_group_unused', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 3, 'product': 1, 'authorization_group': 8}" self._test_api( APITest( "db_internal_write", @@ -123,7 +123,7 @@ def test_authorization_product_authorization_group_members(self): ) ) - expected_data = "{'id': 2, 'authorization_group_name': 'db_group_internal_read', 'role': 2, 'product': 1, 'authorization_group': 5}" + expected_data = "{'id': 2, 'authorization_group_data': {'id': 5, 'name': 'db_group_internal_read', 'oidc_group': ''}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 2, 'product': 1, 'authorization_group': 5}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_product_members.py b/backend/unittests/access_control/api/test_authorization_product_members.py index 41a5560e1..9ef825ae3 100644 --- a/backend/unittests/access_control/api/test_authorization_product_members.py +++ b/backend/unittests/access_control/api/test_authorization_product_members.py @@ -6,14 +6,14 @@ class TestAuthorizationProductMembers(TestAuthorizationBase): def test_authorization_product_members(self): - expected_data = "{'count': 5, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_read', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:25:06+01:00', 'has_password': False}, 'role': 1, 'product': 1, 'user': 3}, {'id': 3, 'user_data': {'id': 4, 'username': 'db_external', 'first_name': '', 'last_name': '', 'full_name': 'db_external', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': True, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-12T19:48:08.514000+01:00', 'has_password': False}, 'role': 5, 'product': 2, 'user': 4}, {'id': 4, 'user_data': {'id': 3, 'username': 'db_internal_read', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_read', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:25:06+01:00', 'has_password': False}, 'role': 1, 'product': 2, 'user': 3}, {'id': 6, 'user_data': {'id': 6, 'username': 'db_product_group_user', 'first_name': '', 'last_name': '', 'full_name': 'db_product_group_user', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-04T11:09:18.495000+01:00', 'has_password': True}, 'role': 5, 'product': 3, 'user': 6}]}" + expected_data = "{'count': 5, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_read', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:25:06+01:00', 'has_password': False}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'user': 3}, {'id': 3, 'user_data': {'id': 4, 'username': 'db_external', 'first_name': '', 'last_name': '', 'full_name': 'db_external', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': True, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-12T19:48:08.514000+01:00', 'has_password': False}, 'product_data': {'id': 2, 'name': 'db_product_external', 'is_product_group': False}, 'role': 5, 'product': 2, 'user': 4}, {'id': 4, 'user_data': {'id': 3, 'username': 'db_internal_read', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_read', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:25:06+01:00', 'has_password': False}, 'product_data': {'id': 2, 'name': 'db_product_external', 'is_product_group': False}, 'role': 1, 'product': 2, 'user': 3}, {'id': 6, 'user_data': {'id': 6, 'username': 'db_product_group_user', 'first_name': '', 'last_name': '', 'full_name': 'db_product_group_user', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-04T11:09:18.495000+01:00', 'has_password': True}, 'product_data': {'id': 3, 'name': 'db_product_group', 'is_product_group': True}, 'role': 5, 'product': 3, 'user': 6}]}" self._test_api( APITest( "db_admin", "get", "/api/product_members/", None, 200, expected_data ) ) - expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'full_name': 'db_internal_read'}, 'role': 1, 'product': 1, 'user': 3}]}" + expected_data = "{'count': 2, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'full_name': 'db_internal_read'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'user': 3}]}" self._test_api( APITest( "db_internal_write", @@ -26,7 +26,7 @@ def test_authorization_product_members(self): ) ) - expected_data = "{'count': 3, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'full_name': 'db_internal_write'}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'full_name': 'db_internal_read'}, 'role': 1, 'product': 1, 'user': 3}, {'id': 6, 'user_data': {'id': 6, 'username': 'db_product_group_user', 'first_name': '', 'last_name': '', 'full_name': 'db_product_group_user', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-04T11:09:18.495000+01:00', 'has_password': True}, 'role': 5, 'product': 3, 'user': 6}]}" + expected_data = "{'count': 3, 'next': None, 'previous': None, 'results': [{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'full_name': 'db_internal_write'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'user': 2}, {'id': 2, 'user_data': {'id': 3, 'username': 'db_internal_read', 'full_name': 'db_internal_read'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 1, 'product': 1, 'user': 3}, {'id': 6, 'user_data': {'id': 6, 'username': 'db_product_group_user', 'first_name': '', 'last_name': '', 'full_name': 'db_product_group_user', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-04T11:09:18.495000+01:00', 'has_password': True}, 'product_data': {'id': 3, 'name': 'db_product_group', 'is_product_group': True}, 'role': 5, 'product': 3, 'user': 6}]}" self._test_api( APITest( "db_product_group_user", @@ -37,8 +37,8 @@ def test_authorization_product_members(self): expected_data, ) ) - expected_data = "{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'role': 5, 'product': 1, 'user': 2}" - expected_data_product_group = "{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'full_name': 'db_internal_write'}, 'role': 5, 'product': 1, 'user': 2}" + expected_data = "{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'user': 2}" + expected_data_product_group = "{'id': 1, 'user_data': {'id': 2, 'username': 'db_internal_write', 'full_name': 'db_internal_write'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 5, 'product': 1, 'user': 2}" self._test_api( APITest( username="db_internal_write", @@ -89,7 +89,7 @@ def test_authorization_product_members(self): expected_data, ) ) - expected_data = "{'id': 7, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'role': 3, 'product': 1, 'user': 1}" + expected_data = "{'id': 7, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 3, 'product': 1, 'user': 1}" self._test_api( APITest( "db_internal_write", @@ -116,7 +116,7 @@ def test_authorization_product_members(self): ) ) - expected_data = "{'id': 7, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'role': 2, 'product': 1, 'user': 1}" + expected_data = "{'id': 7, 'user_data': {'id': 1, 'username': 'db_admin', 'full_name': 'db_admin'}, 'product_data': {'id': 1, 'name': 'db_product_internal', 'is_product_group': False}, 'role': 2, 'product': 1, 'user': 1}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_authorization_settings.py b/backend/unittests/access_control/api/test_authorization_settings.py index 66b701011..bc66ac843 100644 --- a/backend/unittests/access_control/api/test_authorization_settings.py +++ b/backend/unittests/access_control/api/test_authorization_settings.py @@ -6,7 +6,7 @@ class TestAuthorizationSettings(TestAuthorizationBase): def test_authorization_settings(self): - expected_data = "{'id': 1, 'security_gate_active': True, 'security_gate_threshold_critical': 0, 'security_gate_threshold_high': 0, 'security_gate_threshold_medium': 99999, 'security_gate_threshold_low': 99999, 'security_gate_threshold_none': 99999, 'security_gate_threshold_unkown': 99999, 'jwt_validity_duration_user': 168, 'jwt_validity_duration_superuser': 24, 'internal_users': '', 'base_url_frontend': '', 'exception_ms_teams_webhook': '', 'exception_slack_webhook': '', 'exception_rate_limit': 3600, 'email_from': '', 'exception_email_to': '', 'background_product_metrics_interval_minutes': 5, 'background_epss_import_crontab_minute': 0, 'background_epss_import_crontab_hour': 3, 'branch_housekeeping_crontab_minute': 0, 'branch_housekeeping_crontab_hour': 2, 'branch_housekeeping_active': True, 'branch_housekeeping_keep_inactive_days': 30, 'branch_housekeeping_exempt_branches': '', 'feature_vex': False, 'feature_disable_user_login': False, 'feature_general_rules_need_approval': False, 'risk_acceptance_expiry_days': 30, 'risk_acceptance_expiry_crontab_minute': 0, 'risk_acceptance_expiry_crontab_hour': 1, 'feature_automatic_api_import': True, 'api_import_crontab_minute': 0, 'api_import_crontab_hour': 4}" + expected_data = "{'id': 1, 'security_gate_active': True, 'security_gate_threshold_critical': 0, 'security_gate_threshold_high': 0, 'security_gate_threshold_medium': 99999, 'security_gate_threshold_low': 99999, 'security_gate_threshold_none': 99999, 'security_gate_threshold_unkown': 99999, 'jwt_validity_duration_user': 168, 'jwt_validity_duration_superuser': 24, 'internal_users': '', 'base_url_frontend': '', 'exception_ms_teams_webhook': '', 'exception_slack_webhook': '', 'exception_rate_limit': 3600, 'email_from': '', 'exception_email_to': '', 'background_product_metrics_interval_minutes': 5, 'background_epss_import_crontab_minute': 0, 'background_epss_import_crontab_hour': 3, 'branch_housekeeping_crontab_minute': 0, 'branch_housekeeping_crontab_hour': 2, 'branch_housekeeping_active': True, 'branch_housekeeping_keep_inactive_days': 30, 'branch_housekeeping_exempt_branches': '', 'feature_vex': False, 'feature_disable_user_login': False, 'feature_general_rules_need_approval': False, 'risk_acceptance_expiry_days': 30, 'risk_acceptance_expiry_crontab_minute': 0, 'risk_acceptance_expiry_crontab_hour': 1, 'feature_automatic_api_import': True, 'api_import_crontab_minute': 0, 'api_import_crontab_hour': 4, 'password_validator_minimum_length': 8, 'password_validator_attribute_similarity': True, 'password_validator_common_passwords': True, 'password_validator_not_numeric': True}" self._test_api( APITest("db_admin", "get", "/api/settings/1/", None, 200, expected_data) ) @@ -16,7 +16,7 @@ def test_authorization_settings(self): ) post_data = {"security_gate_threshold_critical": 1234} - expected_data = "{'id': 1, 'security_gate_active': True, 'security_gate_threshold_critical': 1234, 'security_gate_threshold_high': 0, 'security_gate_threshold_medium': 99999, 'security_gate_threshold_low': 99999, 'security_gate_threshold_none': 99999, 'security_gate_threshold_unkown': 99999, 'jwt_validity_duration_user': 168, 'jwt_validity_duration_superuser': 24, 'internal_users': '', 'base_url_frontend': '', 'exception_ms_teams_webhook': '', 'exception_slack_webhook': '', 'exception_rate_limit': 3600, 'email_from': '', 'exception_email_to': '', 'background_product_metrics_interval_minutes': 5, 'background_epss_import_crontab_minute': 0, 'background_epss_import_crontab_hour': 3, 'branch_housekeeping_crontab_minute': 0, 'branch_housekeeping_crontab_hour': 2, 'branch_housekeeping_active': True, 'branch_housekeeping_keep_inactive_days': 30, 'branch_housekeeping_exempt_branches': '', 'feature_vex': False, 'feature_disable_user_login': False, 'feature_general_rules_need_approval': False, 'risk_acceptance_expiry_days': 30, 'risk_acceptance_expiry_crontab_minute': 0, 'risk_acceptance_expiry_crontab_hour': 1, 'feature_automatic_api_import': True, 'api_import_crontab_minute': 0, 'api_import_crontab_hour': 4}" + expected_data = "{'id': 1, 'security_gate_active': True, 'security_gate_threshold_critical': 1234, 'security_gate_threshold_high': 0, 'security_gate_threshold_medium': 99999, 'security_gate_threshold_low': 99999, 'security_gate_threshold_none': 99999, 'security_gate_threshold_unkown': 99999, 'jwt_validity_duration_user': 168, 'jwt_validity_duration_superuser': 24, 'internal_users': '', 'base_url_frontend': '', 'exception_ms_teams_webhook': '', 'exception_slack_webhook': '', 'exception_rate_limit': 3600, 'email_from': '', 'exception_email_to': '', 'background_product_metrics_interval_minutes': 5, 'background_epss_import_crontab_minute': 0, 'background_epss_import_crontab_hour': 3, 'branch_housekeeping_crontab_minute': 0, 'branch_housekeeping_crontab_hour': 2, 'branch_housekeeping_active': True, 'branch_housekeeping_keep_inactive_days': 30, 'branch_housekeeping_exempt_branches': '', 'feature_vex': False, 'feature_disable_user_login': False, 'feature_general_rules_need_approval': False, 'risk_acceptance_expiry_days': 30, 'risk_acceptance_expiry_crontab_minute': 0, 'risk_acceptance_expiry_crontab_hour': 1, 'feature_automatic_api_import': True, 'api_import_crontab_minute': 0, 'api_import_crontab_hour': 4, 'password_validator_minimum_length': 8, 'password_validator_attribute_similarity': True, 'password_validator_common_passwords': True, 'password_validator_not_numeric': True}" self._test_api( APITest( "db_admin", diff --git a/backend/unittests/access_control/api/test_authorization_users.py b/backend/unittests/access_control/api/test_authorization_users.py index 30f992096..7b5e4c5ac 100644 --- a/backend/unittests/access_control/api/test_authorization_users.py +++ b/backend/unittests/access_control/api/test_authorization_users.py @@ -33,7 +33,7 @@ def test_authorization_users(self): ) ) - expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'authorization_groups': [{'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}]}" + expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'has_authorization_groups': True, 'has_product_group_members': False, 'has_product_members': True}" expected_data_product_group = "{'id': 2, 'username': 'db_internal_write', 'full_name': 'db_internal_write'}" self._test_api( APITest( @@ -64,7 +64,7 @@ def test_authorization_users(self): self._test_api( APITest("db_external", "get", "/api/users/", None, 200, expected_data) ) - expected_data = "{'id': 4, 'username': 'db_external', 'first_name': '', 'last_name': '', 'full_name': 'db_external', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': True, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-12T19:48:08.514000+01:00', 'has_password': False, 'authorization_groups': []}" + expected_data = "{'id': 4, 'username': 'db_external', 'first_name': '', 'last_name': '', 'full_name': 'db_external', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': True, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-12T19:48:08.514000+01:00', 'has_password': False, 'has_authorization_groups': False, 'has_product_group_members': False, 'has_product_members': True}" self._test_api( APITest("db_external", "get", "/api/users/4/", None, 200, expected_data) ) @@ -214,7 +214,7 @@ def test_authorization_users(self): ) ) - expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'authorization_groups': [{'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}]}" + expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'light', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'has_authorization_groups': True, 'has_product_group_members': False, 'has_product_members': True}" self._test_api( APITest( "db_internal_write", @@ -228,7 +228,7 @@ def test_authorization_users(self): ) post_data = {"setting_theme": "dark"} - expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'dark', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'authorization_groups': [{'id': 3, 'name': 'non_oidc_group', 'oidc_group': ''}]}" + expected_data = "{'id': 2, 'username': 'db_internal_write', 'first_name': '', 'last_name': '', 'full_name': 'db_internal_write', 'email': '', 'is_active': True, 'is_superuser': False, 'is_external': False, 'setting_theme': 'dark', 'setting_list_size': 'medium', 'permissions': [, ], 'setting_list_properties': '', 'oidc_groups_hash': '', 'is_oidc_user': False, 'date_joined': '2022-12-07T20:24:53+01:00', 'has_password': False, 'has_authorization_groups': True, 'has_product_group_members': False, 'has_product_members': True}" self._test_api( APITest( "db_internal_write", diff --git a/backend/unittests/access_control/api/test_serializers.py b/backend/unittests/access_control/api/test_serializers.py new file mode 100644 index 000000000..6bdb384be --- /dev/null +++ b/backend/unittests/access_control/api/test_serializers.py @@ -0,0 +1,91 @@ +from unittest.mock import patch + +from rest_framework.serializers import ValidationError + +from application.access_control.api.serializers import ( + AuthorizationGroupMemberSerializer, +) +from application.access_control.models import Authorization_Group +from unittests.base_test_case import BaseTestCase + + +class TestAuthorizationGroupMemberSerializer(BaseTestCase): + def test_validate_authorization_group_change(self): + auhorization_group_2 = Authorization_Group.objects.create(name="group_2") + authorization_group_member_serializer = AuthorizationGroupMemberSerializer( + self.authorization_group_member_1 + ) + attrs = { + "authorization_group": auhorization_group_2, + } + + with self.assertRaises(ValidationError) as e: + authorization_group_member_serializer.validate(attrs) + + self.assertEqual( + "[ErrorDetail(string='Authorization group and user cannot be changed', code='invalid')]", + str(e.exception), + ) + + def test_validate_user_change(self): + authorization_group_member_serializer = AuthorizationGroupMemberSerializer( + self.authorization_group_member_1 + ) + attrs = { + "user": self.user_external, + } + + with self.assertRaises(ValidationError) as e: + authorization_group_member_serializer.validate(attrs) + + self.assertEqual( + "[ErrorDetail(string='Authorization group and user cannot be changed', code='invalid')]", + str(e.exception), + ) + + @patch("application.access_control.api.serializers.get_authorization_group_member") + def test_validate_already_exists(self, mock_authorization_group_member): + mock_authorization_group_member.return_value = self.authorization_group_member_1 + authorization_group_member_serializer = AuthorizationGroupMemberSerializer() + attrs = { + "authorization_group": self.authorization_group_1, + "user": self.user_internal, + } + + with self.assertRaises(ValidationError) as e: + authorization_group_member_serializer.validate(attrs) + + self.assertEqual( + "[ErrorDetail(string='Authorization group member authorization_group_1 / user_internal@example.com already exists', code='invalid')]", + str(e.exception), + ) + mock_authorization_group_member.assert_called_with( + self.authorization_group_1, self.user_internal + ) + + def test_validate_successful_with_instance(self): + authorization_group_member_serializer = AuthorizationGroupMemberSerializer( + self.authorization_group_member_1 + ) + attrs = {"is_manager": False} + + new_attrs = authorization_group_member_serializer.validate(attrs) + + self.assertEqual(new_attrs, attrs) + + @patch("application.access_control.api.serializers.get_authorization_group_member") + def test_validate_successful_no_instance(self, mock_authorization_group_member): + mock_authorization_group_member.return_value = None + authorization_group_member_serializer = AuthorizationGroupMemberSerializer() + attrs = { + "authorization_group": self.authorization_group_1, + "user": self.user_external, + "is_manager": False, + } + + new_attrs = authorization_group_member_serializer.validate(attrs) + + self.assertEqual(new_attrs, attrs) + mock_authorization_group_member.assert_called_with( + self.authorization_group_1, self.user_external + ) diff --git a/backend/unittests/access_control/services/test_authorization.py b/backend/unittests/access_control/services/test_authorization.py index 26ddf55ad..bb54adf7b 100644 --- a/backend/unittests/access_control/services/test_authorization.py +++ b/backend/unittests/access_control/services/test_authorization.py @@ -3,7 +3,11 @@ from django.core.management import call_command from rest_framework.exceptions import PermissionDenied -from application.access_control.models import Authorization_Group, User +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, + User, +) from application.access_control.services.authorization import ( NoAuthorizationImplementedError, PermissionDoesNotExistError, @@ -461,7 +465,14 @@ def prepare_authorization_groups(): group_internal_write = Authorization_Group.objects.create( name="db_group_internal_write" ) - group_internal_write.users.add(user_internal_write) + Authorization_Group_Member.objects.filter( + authorization_group=group_internal_write + ).delete() + Authorization_Group_Member.objects.create( + authorization_group=group_internal_write, + user=user_internal_write, + is_manager=True, + ) Product_Authorization_Group_Member.objects.create( product=product_internal, authorization_group=group_internal_write, role=5 ) @@ -469,13 +480,27 @@ def prepare_authorization_groups(): group_internal_read = Authorization_Group.objects.create( name="db_group_internal_read" ) - group_internal_read.users.add(User.objects.get(id=3)) + Authorization_Group_Member.objects.filter( + authorization_group=group_internal_read + ).delete() + Authorization_Group_Member.objects.create( + authorization_group=group_internal_read, + user=User.objects.get(id=3), + is_manager=False, + ) Product_Authorization_Group_Member.objects.create( product=product_internal, authorization_group=group_internal_read, role=1 ) group_external = Authorization_Group.objects.create(name="db_group_external") - group_external.users.add(User.objects.get(id=4)) + Authorization_Group_Member.objects.filter( + authorization_group=group_external + ).delete() + Authorization_Group_Member.objects.create( + authorization_group=group_external, + user=User.objects.get(id=4), + is_manager=False, + ) Product_Authorization_Group_Member.objects.create( product=product_external, authorization_group=group_external, role=5 ) @@ -483,9 +508,17 @@ def prepare_authorization_groups(): group_product_group = Authorization_Group.objects.create( name="db_group_product_group" ) - group_product_group.users.add(User.objects.get(id=6)) + Authorization_Group_Member.objects.filter( + authorization_group=group_product_group + ).delete() + Authorization_Group_Member.objects.create( + authorization_group=group_product_group, + user=User.objects.get(id=6), + is_manager=False, + ) Product_Authorization_Group_Member.objects.create( product=product_group, authorization_group=group_product_group, role=5 ) - group_product_group = Authorization_Group.objects.create(name="db_group_unused") + group_unused = Authorization_Group.objects.create(name="db_group_unused") + Authorization_Group_Member.objects.filter(authorization_group=group_unused).delete() diff --git a/backend/unittests/base_test_case.py b/backend/unittests/base_test_case.py index 818d76efa..db4719235 100644 --- a/backend/unittests/base_test_case.py +++ b/backend/unittests/base_test_case.py @@ -1,6 +1,10 @@ from django.test import TestCase -from application.access_control.models import Authorization_Group, User +from application.access_control.models import ( + Authorization_Group, + Authorization_Group_Member, + User, +) from application.access_control.services.roles_permissions import Roles from application.core.models import ( Branch, @@ -63,6 +67,11 @@ def setUp(self) -> None: ) self.authorization_group_1 = Authorization_Group(name="authorization_group_1") + self.authorization_group_member_1 = Authorization_Group_Member( + authorization_group=self.authorization_group_1, + user=self.user_internal, + is_manager=True, + ) self.product_authorization_group_member_1 = Product_Authorization_Group_Member( product=self.product_1, authorization_group=self.authorization_group_1, diff --git a/backend/unittests/fixtures/unittests_fixtures.json b/backend/unittests/fixtures/unittests_fixtures.json index d0f241d76..5e8541823 100644 --- a/backend/unittests/fixtures/unittests_fixtures.json +++ b/backend/unittests/fixtures/unittests_fixtures.json @@ -132,8 +132,7 @@ "pk": 1, "fields": { "name": "oidc_group_1", - "oidc_group": "oidc_1", - "users": [] + "oidc_group": "oidc_1" } }, { @@ -141,8 +140,7 @@ "pk": 2, "fields": { "name": "oidc_group_2", - "oidc_group": "oidc_2", - "users": [] + "oidc_group": "oidc_2" } }, { @@ -150,8 +148,25 @@ "pk": 3, "fields": { "name": "non_oidc_group", - "oidc_group": "", - "users": [2, 3] + "oidc_group": "" + } + }, + { + "model": "access_control.authorization_group_member", + "pk": 1, + "fields": { + "authorization_group": 3, + "user": 2, + "is_manager": true + } + }, + { + "model": "access_control.authorization_group_member", + "pk": 2, + "fields": { + "authorization_group": 3, + "user": 3, + "is_manager": false } }, { diff --git a/backend/unittests/vex/api/files/csaf_given_vulnerability.json b/backend/unittests/vex/api/files/csaf_given_vulnerability.json index 8ea751883..681727693 100644 --- a/backend/unittests/vex/api/files/csaf_given_vulnerability.json +++ b/backend/unittests/vex/api/files/csaf_given_vulnerability.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0001", diff --git a/backend/unittests/vex/api/files/csaf_given_vulnerability_update.json b/backend/unittests/vex/api/files/csaf_given_vulnerability_update.json index 14f8d7e1d..07bb9ca3c 100644 --- a/backend/unittests/vex/api/files/csaf_given_vulnerability_update.json +++ b/backend/unittests/vex/api/files/csaf_given_vulnerability_update.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0002", diff --git a/backend/unittests/vex/api/files/csaf_product_branches.json b/backend/unittests/vex/api/files/csaf_product_branches.json index 6d8bf129d..b41a99fb8 100644 --- a/backend/unittests/vex/api/files/csaf_product_branches.json +++ b/backend/unittests/vex/api/files/csaf_product_branches.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0001", diff --git a/backend/unittests/vex/api/files/csaf_product_given_branch.json b/backend/unittests/vex/api/files/csaf_product_given_branch.json index 7204e7d8f..cf20c0a7c 100644 --- a/backend/unittests/vex/api/files/csaf_product_given_branch.json +++ b/backend/unittests/vex/api/files/csaf_product_given_branch.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0001", diff --git a/backend/unittests/vex/api/files/csaf_product_no_branch.json b/backend/unittests/vex/api/files/csaf_product_no_branch.json index b0dee5766..124ac5f71 100644 --- a/backend/unittests/vex/api/files/csaf_product_no_branch.json +++ b/backend/unittests/vex/api/files/csaf_product_no_branch.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0001", diff --git a/backend/unittests/vex/api/files/csaf_product_no_branch_update.json b/backend/unittests/vex/api/files/csaf_product_no_branch_update.json index 5d85ea206..42e69b355 100644 --- a/backend/unittests/vex/api/files/csaf_product_no_branch_update.json +++ b/backend/unittests/vex/api/files/csaf_product_no_branch_update.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "CSAF_2024_0001_0002", diff --git a/backend/unittests/vex/api/files/openvex_given_vulnerability.json b/backend/unittests/vex/api/files/openvex_given_vulnerability.json index a883ae2b3..af575b481 100644 --- a/backend/unittests/vex/api/files/openvex_given_vulnerability.json +++ b/backend/unittests/vex/api/files/openvex_given_vulnerability.json @@ -47,6 +47,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 1 } \ No newline at end of file diff --git a/backend/unittests/vex/api/files/openvex_given_vulnerability_update.json b/backend/unittests/vex/api/files/openvex_given_vulnerability_update.json index 92fadf67b..fc3cbf780 100644 --- a/backend/unittests/vex/api/files/openvex_given_vulnerability_update.json +++ b/backend/unittests/vex/api/files/openvex_given_vulnerability_update.json @@ -37,6 +37,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 2 } \ No newline at end of file diff --git a/backend/unittests/vex/api/files/openvex_product_branches.json b/backend/unittests/vex/api/files/openvex_product_branches.json index 13193608d..25958d949 100644 --- a/backend/unittests/vex/api/files/openvex_product_branches.json +++ b/backend/unittests/vex/api/files/openvex_product_branches.json @@ -70,6 +70,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 1 } \ No newline at end of file diff --git a/backend/unittests/vex/api/files/openvex_product_given_branch.json b/backend/unittests/vex/api/files/openvex_product_given_branch.json index 9fbf2e08a..e433c6010 100644 --- a/backend/unittests/vex/api/files/openvex_product_given_branch.json +++ b/backend/unittests/vex/api/files/openvex_product_given_branch.json @@ -28,6 +28,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 1 } \ No newline at end of file diff --git a/backend/unittests/vex/api/files/openvex_product_no_branch.json b/backend/unittests/vex/api/files/openvex_product_no_branch.json index 506820670..e33721ebf 100644 --- a/backend/unittests/vex/api/files/openvex_product_no_branch.json +++ b/backend/unittests/vex/api/files/openvex_product_no_branch.json @@ -63,6 +63,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 1 } \ No newline at end of file diff --git a/backend/unittests/vex/api/files/openvex_product_no_branch_update.json b/backend/unittests/vex/api/files/openvex_product_no_branch_update.json index 3de041876..12884c500 100644 --- a/backend/unittests/vex/api/files/openvex_product_no_branch_update.json +++ b/backend/unittests/vex/api/files/openvex_product_no_branch_update.json @@ -64,6 +64,6 @@ } ], "timestamp": "2020-01-01T04:30:00+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 2 } \ No newline at end of file diff --git a/backend/unittests/vex/services/files/so_csaf_2024_0001_0001.json b/backend/unittests/vex/services/files/so_csaf_2024_0001_0001.json index 90bf847e9..7738596ec 100644 --- a/backend/unittests/vex/services/files/so_csaf_2024_0001_0001.json +++ b/backend/unittests/vex/services/files/so_csaf_2024_0001_0001.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "so_2024_0001_0001", diff --git a/backend/unittests/vex/services/files/so_csaf_2024_0001_0001_short.json b/backend/unittests/vex/services/files/so_csaf_2024_0001_0001_short.json index 07cd052a4..eb5f0f89b 100644 --- a/backend/unittests/vex/services/files/so_csaf_2024_0001_0001_short.json +++ b/backend/unittests/vex/services/files/so_csaf_2024_0001_0001_short.json @@ -18,7 +18,7 @@ "generator": { "engine": { "name": "SecObserve", - "version": "1.19.0" + "version": "1.20.0" } }, "id": "so_2024_0001_0001", diff --git a/backend/unittests/vex/services/files/so_openvex_2024_0001_0001.json b/backend/unittests/vex/services/files/so_openvex_2024_0001_0001.json index f68d8de70..45c8793d5 100644 --- a/backend/unittests/vex/services/files/so_openvex_2024_0001_0001.json +++ b/backend/unittests/vex/services/files/so_openvex_2024_0001_0001.json @@ -294,6 +294,6 @@ } ], "timestamp": "2024-07-14T11:17:57.668593+00:00", - "tooling": "SecObserve / 1.19.0", + "tooling": "SecObserve / 1.20.0", "version": 1 } \ No newline at end of file diff --git a/docker-compose-dev-keycloak.yml b/docker-compose-dev-keycloak.yml index e05b84c66..451a43cf2 100644 --- a/docker-compose-dev-keycloak.yml +++ b/docker-compose-dev-keycloak.yml @@ -84,7 +84,7 @@ services: - "8025:8025" keycloak: - image: keycloak/keycloak:25.0.6 + image: keycloak/keycloak:26.0.0 environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin diff --git a/docker-compose-playwright.yml b/docker-compose-playwright.yml index c09dfde63..44580287a 100644 --- a/docker-compose-playwright.yml +++ b/docker-compose-playwright.yml @@ -58,7 +58,7 @@ services: playwright: - image: mcr.microsoft.com/playwright:v1.47.2 + image: mcr.microsoft.com/playwright:v1.48.0 depends_on: - frontend environment: diff --git a/docker-compose-prod-mysql.yml b/docker-compose-prod-mysql.yml index 84fa485d8..70ee64901 100644 --- a/docker-compose-prod-mysql.yml +++ b/docker-compose-prod-mysql.yml @@ -10,7 +10,7 @@ networks: services: traefik: - image: "traefik:v3.1.5" + image: "traefik:v3.1.6" container_name: "prod_traefik" command: - "--log.level=INFO" @@ -35,7 +35,7 @@ services: - traefik frontend: - image: maibornwolff/secobserve-frontend:1.19.0 + image: maibornwolff/secobserve-frontend:1.20.0 container_name: "prod_secobserve_frontend" labels: - "traefik.enable=true" @@ -54,7 +54,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.19.0 + image: maibornwolff/secobserve-backend:1.20.0 container_name: "prod_secobserve_backend" labels: - "traefik.enable=true" diff --git a/docker-compose-prod-postgres.yml b/docker-compose-prod-postgres.yml index 1cd8c77e7..f08d3e53d 100644 --- a/docker-compose-prod-postgres.yml +++ b/docker-compose-prod-postgres.yml @@ -10,7 +10,7 @@ networks: services: traefik: - image: "traefik:v3.1.5" + image: "traefik:v3.1.6" container_name: "prod_traefik" command: - "--log.level=INFO" @@ -35,7 +35,7 @@ services: - traefik frontend: - image: maibornwolff/secobserve-frontend:1.19.0 + image: maibornwolff/secobserve-frontend:1.20.0 container_name: "prod_secobserve_frontend" labels: - "traefik.enable=true" @@ -54,7 +54,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.19.0 + image: maibornwolff/secobserve-backend:1.20.0 container_name: "prod_secobserve_backend" labels: - "traefik.enable=true" diff --git a/docker/frontend/dev/Dockerfile b/docker/frontend/dev/Dockerfile index 15a3dd7c0..f8a1d1cc5 100644 --- a/docker/frontend/dev/Dockerfile +++ b/docker/frontend/dev/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.17.0-alpine3.19@sha256:20b236009deb4c33bf7e255c32fe1e82da7eb0a10245657e0a6a846851fde521 +FROM node:20.18.0-alpine3.19@sha256:2d8c24d9104bda27e07dced6d7110aa728dd917dde8255d8af3678e532b339d6 WORKDIR /app COPY ./frontend/package*.json /app/. diff --git a/docker/frontend/prod/Dockerfile b/docker/frontend/prod/Dockerfile index 42a9bbd0c..f409b25a1 100644 --- a/docker/frontend/prod/Dockerfile +++ b/docker/frontend/prod/Dockerfile @@ -1,5 +1,5 @@ # build environment -FROM node:20.17.0-alpine3.19@sha256:20b236009deb4c33bf7e255c32fe1e82da7eb0a10245657e0a6a846851fde521 AS build +FROM node:20.18.0-alpine3.19@sha256:2d8c24d9104bda27e07dced6d7110aa728dd917dde8255d8af3678e532b339d6 AS build ARG VERSION=unkown diff --git a/docs/assets/images/secobserve_process.drawio b/docs/assets/images/secobserve_process.drawio index 6896bc553..91a1834aa 100644 --- a/docs/assets/images/secobserve_process.drawio +++ b/docs/assets/images/secobserve_process.drawio @@ -1,50 +1,53 @@ - + - + - + - + - + - + - - + + - + - - + + - + - - + + - - + + - - + + - - + + + + + diff --git a/docs/assets/images/secobserve_process.svg b/docs/assets/images/secobserve_process.svg index 76e2584b7..97df3073e 100644 --- a/docs/assets/images/secobserve_process.svg +++ b/docs/assets/images/secobserve_process.svg @@ -1,3 +1,3 @@ -
Project's CI/CD Pipeline
View observations
Assess observations
Upload reports manually
GitLab CI Templates / GitHub Actions
SCA


Dep. Check
Dep. Track
Grype
Trivy
SAST
Application

Bandit
ESLint
FindSecBugs
Semgrep
SAST
Infrastructure

Checkov
KICS
tfsec
Trivy
DAST


Cryptolyzer
DrHeader
ZAP
Cloud
Infastructure


Prowler
Azure Defender
Trivy Operator
Secrets


GitLeaks
Upload
Check
Security Gate
Scan
\ No newline at end of file +
Project's CI/CD Pipeline
View observations
Assess observations
Upload reports manually
GitLab CI Templates / GitHub Actions
SCA


Dep. Check
Dep. Track
Grype
Trivy (Operator)
SAST
Application

Bandit
ESLint
FindSecBugs
Semgrep
SAST
Infrastructure

Checkov
KICS
Trivy
DAST


Cryptolyzer
DrHeader
ZAP
Cloud
Infastructure


Prowler
Azure Defender
Trivy Operator
Secrets


GitLeaks
Trivy (Operator)
Upload
Check
Security Gate
Scan
\ No newline at end of file diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index b9a1e7497..75962102a 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -45,7 +45,7 @@ services: - default frontend: - image: maibornwolff/secobserve-frontend:1.19.0 + image: maibornwolff/secobserve-frontend:1.20.0 labels: - "traefik.enable=true" - "traefik.http.routers.frontend.rule=Host(`secobserve.localhost`)" @@ -62,7 +62,7 @@ services: - traefik backend: - image: maibornwolff/secobserve-backend:1.19.0 + image: maibornwolff/secobserve-backend:1.20.0 labels: - "traefik.enable=true" - "traefik.http.routers.backend.rule=Host(`secobserve-backend.localhost`)" diff --git a/docs/index.md b/docs/index.md index e872fbead..95fd38854 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ --- template: home.html -title: Material for MkDocs +title: SecObserve Documentation --- diff --git a/docs/integrations/github_actions_and_templates.md b/docs/integrations/github_actions_and_templates.md index 82560529a..8cbada45e 100644 --- a/docs/integrations/github_actions_and_templates.md +++ b/docs/integrations/github_actions_and_templates.md @@ -35,17 +35,24 @@ Most of the actions and templates use the same set of variables: | Scanner | GitHub Action | GitLab CI Template | License | |----------|---------|-------------|--------| +| **SCA** | +| [Grype](https://github.com/anchore/grype) | `actions/SCA/grype_image` | `templates/SCA/grype_image.yml` | [Apache 2.0](https://github.com/anchore/grype/blob/main/LICENSE) | +| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SCA/trivy_filesystem` | `templates/SCA/trivy_filesystem.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SCA/trivy_image` | `templates/SCA/trivy_image.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| **SAST application** | | [Bandit](https://bandit.readthedocs.io/en/latest) | `actions/SAST/bandit` | `templates/SAST/bandit.yml` | [Apache 2.0](https://github.com/PyCQA/bandit/blob/main/LICENSE) | | [ESLint](https://github.com/eslint/eslint) | `actions/SAST/eslint` | `templates/SAST/eslint.yml` | [MIT](https://github.com/eslint/eslint/blob/main/LICENSE) | | [Semgrep](https://semgrep.dev/docs) | `actions/SAST/semgrep` | `templates/SAST/semgrep.yml` |[LGPL 2.1](https://github.com/returntocorp/semgrep/blob/develop/LICENSE) | +| **SAST infrastructure** | | [Checkov](https://www.checkov.io/1.Welcome/Quick%20Start.html) | `actions/SAST/checkov` | `templates/SAST/checkov.yml` | [Apache 2.0](https://github.com/bridgecrewio/checkov/blob/main/LICENSE) | | [KICS](https://docs.kics.io/latest) | `actions/SAST/kics` | `templates/SAST/kics.yml` | [Apache 2.0](https://github.com/Checkmarx/kics/blob/master/LICENSE) | | [tfsec](https://aquasecurity.github.io/tfsec) | `actions/SAST/tfsec` | `templates/SAST/tfsec.yml` | [MIT](https://github.com/aquasecurity/tfsec/blob/master/LICENSE) | -| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SCA/trivy_config` | `templates/SCA/trivy_config.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | -| [Grype](https://github.com/anchore/grype) | `actions/SCA/grype_image` | `templates/SCA/grype_image.yml` | [Apache 2.0](https://github.com/anchore/grype/blob/main/LICENSE) | -| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SCA/trivy_filesystem` | `templates/SCA/trivy_filesystem.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | -| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SCA/trivy_image` | `templates/SCA/trivy_image.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| [Trivy](https://aquasecurity.github.io/trivy) | `actions/SAST/trivy_config` | `templates/SAST/trivy_config.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| **Secrets** | | [Gitleaks](https://gitleaks.io) | `actions/secrets/gitleaks` | `templates/secrets/gitleaks.yml` | [MIT](https://github.com/gitleaks/gitleaks/blob/master/LICENSE) | +| [Trivy](https://aquasecurity.github.io/trivy) | `actions/secrets/trivy_filesystem_secrets` | `templates/secrets/trivy_filesystem_secrets.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| [Trivy](https://aquasecurity.github.io/trivy) | `actions/secrets/trivy_image_secrets` | `templates/secrets/trivy_image_secrets.yml` | [Apache 2.0](https://github.com/aquasecurity/trivy/blob/main/LICENSE) | +| **DAST** | | [CryptoLyzer](https://gitlab.com/coroner/cryptolyzer) | `actions/DAST/cryptolyzer` | `templates/DAST/cryptolyzer.yml` | [MPL 2.0](https://gitlab.com/coroner/cryptolyzer/-/blob/master/LICENSE.txt) | | [DrHeader](https://github.com/Santandersecurityresearch/DrHeader) | `actions/DAST/drheader` | `templates/DAST/drheader.yml` | [MIT](https://github.com/Santandersecurityresearch/DrHeader/blob/master/LICENSE) | | [ZAP](https://github.com/zaproxy/zaproxy) | `actions/DAST/zap` | `templates/DAST/zap.yml` | [Apache 2.0](https://github.com/zaproxy/zaproxy/blob/main/LICENSE) | diff --git a/docs/integrations/supported_scanners.md b/docs/integrations/supported_scanners.md index 6ab936242..542e8c019 100644 --- a/docs/integrations/supported_scanners.md +++ b/docs/integrations/supported_scanners.md @@ -5,8 +5,8 @@ There are different types of vulnerability scans: * **SCA / Software Composition Analysis**: Modern systems are not completely rewritten from scratch, but many basic functions are used as libraries. This applies not only to application code, but in the case of Docker, also to operating system functions and programmes. All these components can have known vulnerabilities that can be exploited by attackers. -* **Application SAST / Static Application Security Testing**: Many problems can be detected in the code through rule-based searches, e.g. injections or weak encryption. Tools exist for all common programming languages. -* **Infrastructure SAST**: Also for Infrastructure as Code (Dockerfile, Helm Charts, Terraform, ...) many problems can be found with rule-based searches before applying the code to set up the infrastructure. +* **SAST application / Static Application Security Testing**: Many problems can be detected in the code through rule-based searches, e.g. injections or weak encryption. Tools exist for all common programming languages. +* **SAST infrastructure**: Also for Infrastructure as Code (Dockerfile, Helm Charts, Terraform, ...) many problems can be found with rule-based searches before applying the code to set up the infrastructure. * **Secrets**: Secrets such as passwords or API keys must not be checked into repositories with the code, and there are tools that search, for example, Git repositories across the entire version history for such secrets. * **DAST / Dynamic Application Security Testing:**: This class is black-box security testing where the tests are performed by attacking an application (typically web applications or APIs) from the outside. The tests can be passive, where only anomalies are looked for, or active attacks on the system. * **Cloud infrastructure:** The running infrastructure, e.g. a Kubernetes cluster, can also be checked for vulnerabilities both with internal views (tests that run inside the infrastructure) and external views (tests from outside via the internet). @@ -25,29 +25,35 @@ This means the `CycloneDX` and `SARIF` parsers can import data from a variety of These scanners have been tested with SecObserve: -| Scanner | Parser | Type | Source | -|--------|---------|------|--------| -| [Dependency Track](https://dependencytrack.org) | Dependency Track | SCA | [API](../integrations/api_import.md#dependency-track) | -| [Trivy Operator Prometheus](https://github.com/aquasecurity/trivy-operator) | JSON | Cloud infrastructure | [API](../integrations/api_import.md#trivy-operator-prometheus) | -| [Dependency Check](https://jeremylong.github.io/DependencyCheck) | SARIF ^1)^ | SCA | File | -| [Grype](https://github.com/anchore/grype) | CycloneDX | SCA | File | -| [Trivy](https://aquasecurity.github.io/trivy) | CycloneDX | SCA | File | -| [Bandit](https://bandit.readthedocs.io/en/latest) | SARIF | Application SAST | File | -| [ESLint](https://github.com/nodesecurity/eslint-plugin-security) | SARIF | Application SAST | File | -| [Find-Sec-Bugs](https://find-sec-bugs.github.io) | SARIF | Application SAST | File | -| [Semgrep](https://semgrep.dev/docs) | SARIF | Application SAST | File | -| [Checkov](https://www.checkov.io/1.Welcome/Quick%20Start.html) | SARIF | Infrastructure SAST | File | -| [KICS](https://docs.kics.io/latest) | SARIF | Infrastructure SAST | File | -| [tfsec](https://aquasecurity.github.io/tfsec) | SARIF | Infrastructure SAST | File | -| [Trivy](https://aquasecurity.github.io/trivy) | SARIF | Infrastructure SAST | File | -| [Trivy](https://aquasecurity.github.io/trivy) | SARIF | Secrets | File | -| [Gitleaks](https://gitleaks.io) | SARIF | Secrets | File | -| [CryptoLyzer](https://gitlab.com/coroner/cryptolyzer) ^2)^ | CryptoLyzer | DAST | File | -| [DrHeader](https://github.com/Santandersecurityresearch/DrHeader) | DrHeader | DAST | File | -| [ZAP](https://www.zaproxy.org) | ZAP | DAST | File | -| [Azure Defender for Cloud](https://learn.microsoft.com/en-us/azure/defender-for-cloud/) ^3)^ | Azure Defender | Cloud infrastructure | File | -| [Prowler 3](https://github.com/prowler-cloud/prowler)| Prowler 3 | Cloud infrastructure | File | -| [Prowler 4](https://github.com/prowler-cloud/prowler)| OCSF (Open Cybersecurity Schema Framework) | Cloud infrastructure | File | +| Scanner | Parser | Source | +|--------|---------|--------| +| **SCA** | +| [Dependency Track](https://dependencytrack.org) | Dependency Track | [API](../integrations/api_import.md#dependency-track) | +| [Dependency Check](https://jeremylong.github.io/DependencyCheck) | SARIF ^1)^ | File | +| [Grype](https://github.com/anchore/grype) | CycloneDX | File | +| [Trivy](https://aquasecurity.github.io/trivy) | CycloneDX | File | +| **SAST application** | +| [Bandit](https://bandit.readthedocs.io/en/latest) | SARIF | File | +| [ESLint](https://github.com/nodesecurity/eslint-plugin-security) | SARIF | File | +| [Find-Sec-Bugs](https://find-sec-bugs.github.io) | SARIF | File | +| [Semgrep](https://semgrep.dev/docs) | SARIF | File | +| **SAST infrastructure** | +| [Checkov](https://www.checkov.io/1.Welcome/Quick%20Start.html) | SARIF | File | +| [KICS](https://docs.kics.io/latest) | SARIF | File | +| [tfsec](https://aquasecurity.github.io/tfsec) | SARIF | File | +| [Trivy](https://aquasecurity.github.io/trivy) | SARIF | File | +| **Secrets** | +| [Gitleaks](https://gitleaks.io) | SARIF | File | +| [Trivy](https://aquasecurity.github.io/trivy) | SARIF | File | +| **DAST** | +| [CryptoLyzer](https://gitlab.com/coroner/cryptolyzer) ^2)^ | CryptoLyzer | File | +| [DrHeader](https://github.com/Santandersecurityresearch/DrHeader) | DrHeader | File | +| [ZAP](https://www.zaproxy.org) | ZAP | File | +| **Cloud infrastructure** | +| [Azure Defender for Cloud](https://learn.microsoft.com/en-us/azure/defender-for-cloud/) ^3)^ | Azure Defender | File | +| [Prowler 3](https://github.com/prowler-cloud/prowler)| Prowler 3 | File | +| [Prowler 4](https://github.com/prowler-cloud/prowler)| OCSF (Open Cybersecurity Schema Framework) | File | +| [Trivy Operator Prometheus](https://github.com/aquasecurity/trivy-operator) | JSON | [API](../integrations/api_import.md#trivy-operator-prometheus) | ^1)^ This is the exception to the rule. Even though SARIF is more suited for static code analysis, it works for Dependency Check. diff --git a/docs/usage/import_observations.md b/docs/usage/import_observations.md index ff24eef5c..76d256678 100644 --- a/docs/usage/import_observations.md +++ b/docs/usage/import_observations.md @@ -33,13 +33,13 @@ A flowchart visualizes the import algorithm: ```mermaid flowchart TD - read(Read observations for a vulnerability check) --> all_observations{For all observations\nof this vulnerability check} - all_observations -- with observation --> identity{Identical observation\nin the same\nvulnerability check?} + read(Read observations for a vulnerability check) --> all_observations{For all observations of this vulnerability check} + all_observations -- with observation --> identity{Identical observation in the same vulnerability check?} subgraph Create or update identity -- yes --> update(Update existing observation) identity -- no --> create(Create new observation) end - all_observations -- finished ----> resolved(Set status to `Resolved` for all untouched observations of this vulnerability check) + all_observations -- finished ----> resolved(Set status to **Resolved** for all untouched observations of this vulnerability check) ``` diff --git a/docs/usage/users_permissions.md b/docs/usage/users_permissions.md index 216929082..a7c948809 100644 --- a/docs/usage/users_permissions.md +++ b/docs/usage/users_permissions.md @@ -36,7 +36,8 @@ There are some general permissions based on the user's type: | Change own password ^1)^ | X | X | X | | Change all passwords ^1)^ | - | - | X | | Manage users | - | - | X | -| Manage authorization groups | - | - | X | +| Create authorization groups | X | - | X | +| Manage authorization groups | X ^2)^ | X ^2)^ | X | | | | | | | Import VEX documents | - | - | X | | Manage VEX counters | - | - | X | @@ -46,6 +47,8 @@ There are some general permissions based on the user's type: **^1)^** Not for OIDC users +**^2)^** Only if the user is a manager of the authorization group + ## Authorization groups diff --git a/end_to_end_tests/package-lock.json b/end_to_end_tests/package-lock.json index b9486821c..741a41785 100644 --- a/end_to_end_tests/package-lock.json +++ b/end_to_end_tests/package-lock.json @@ -1,25 +1,25 @@ { "name": "end_to_end_tests", - "version": "1.19.0", + "version": "1.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "end_to_end_tests", - "version": "1.19.0", + "version": "1.20.0", "devDependencies": { - "@playwright/test": "1.47.2", - "@types/node": "20.16.10" + "@playwright/test": "1.48.0", + "@types/node": "20.16.11" } }, "node_modules/@playwright/test": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", - "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz", + "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.47.2" + "playwright": "1.48.0" }, "bin": { "playwright": "cli.js" @@ -29,9 +29,9 @@ } }, "node_modules/@types/node": { - "version": "20.16.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz", - "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==", + "version": "20.16.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", + "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, "license": "MIT", "dependencies": { @@ -54,13 +54,13 @@ } }, "node_modules/playwright": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", - "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz", + "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.47.2" + "playwright-core": "1.48.0" }, "bin": { "playwright": "cli.js" @@ -73,9 +73,9 @@ } }, "node_modules/playwright-core": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", - "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz", + "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/end_to_end_tests/package.json b/end_to_end_tests/package.json index 78e630c8d..23f199d33 100644 --- a/end_to_end_tests/package.json +++ b/end_to_end_tests/package.json @@ -1,6 +1,6 @@ { "name": "end_to_end_tests", - "version": "1.19.0", + "version": "1.20.0", "private": true, "description": "", "main": "index.js", @@ -8,7 +8,7 @@ "keywords": [], "author": "", "devDependencies": { - "@playwright/test": "1.47.2", - "@types/node": "20.16.10" + "@playwright/test": "1.48.0", + "@types/node": "20.16.11" } } diff --git a/end_to_end_tests/playwright.config.ts b/end_to_end_tests/playwright.config.ts index 7f098fe12..13c1bedb5 100644 --- a/end_to_end_tests/playwright.config.ts +++ b/end_to_end_tests/playwright.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + // workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [['html', { open: 'never' }]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -30,7 +30,8 @@ export default defineConfig({ trace: 'on-first-retry', }, - timeout: 20000, + workers: 3, + timeout: 60000, /* Configure projects for major browsers */ projects: [ @@ -38,16 +39,14 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, /* Test against mobile viewports. */ // { diff --git a/end_to_end_tests/tests/secobserve_test.spec.ts b/end_to_end_tests/tests/secobserve_test.spec.ts index db1c95e44..841321fb7 100644 --- a/end_to_end_tests/tests/secobserve_test.spec.ts +++ b/end_to_end_tests/tests/secobserve_test.spec.ts @@ -14,17 +14,24 @@ test.describe("SecObserve", async () => { test("Login", async () => { if (process.env.SO_PW_DOCKER) { - await delay(15000); + await delay(30000); } await page.goto(process.env.SO_PW_FRONTEND_BASE_URL); + await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/login"); + + page.on('console', msg => console.log(msg.text())); + await page.getByLabel("Username *").click(); await page.getByLabel("Username *").fill(process.env.SO_PW_USERNAME); await page.getByLabel("Username *").press("Tab"); await page.getByLabel("Password *").fill(process.env.SO_PW_PASSWORD); await page.getByRole("button", { name: "Sign in with user" }).click(); + page.on('console', msg => console.log(msg.text())); + await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/"); + await page.getByRole("menuitem", { name: "Product Groups" }).click(); await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/product_groups"); @@ -33,14 +40,5 @@ test.describe("SecObserve", async () => { await page.getByRole("menuitem", { name: "Observations" }).click(); await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/observations"); - - await page.getByRole("menuitem", { name: "Parsers" }).click(); - await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/parsers"); - - await page.getByRole("menuitem", { name: "General Rules" }).click(); - await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/general_rules"); - - await page.getByRole("menuitem", { name: "Notifications" }).click(); - await expect(page).toHaveURL(process.env.SO_PW_FRONTEND_BASE_URL + "/#/notifications"); }); }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e40eda2e5..9f38b0392 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "secobserve", - "version": "1.19.0", + "version": "1.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "secobserve", - "version": "1.19.0", + "version": "1.20.0", "license": "BSD-3-Clause", "dependencies": { "@emotion/react": "11.13.3", @@ -24,45 +24,45 @@ "mermaid": "11.3.0", "oidc-client-ts": "3.1.0", "prop-types": "15.8.1", - "query-string": "9.1.0", - "ra-i18n-polyglot": "5.2.2", - "ra-input-rich-text": "5.2.2", - "ra-language-english": "5.2.2", + "query-string": "9.1.1", + "ra-i18n-polyglot": "5.2.3", + "ra-input-rich-text": "5.2.3", + "ra-language-english": "5.2.3", "react": "18.3.1", - "react-admin": "5.2.2", + "react-admin": "5.2.3", "react-chartjs-2": "5.2.0", "react-dom": "18.3.1", "react-hook-form": "7.53.0", - "react-oidc-context": "3.1.1", - "react-router": "6.26.2", - "react-router-dom": "6.26.2", + "react-oidc-context": "3.2.0", + "react-router": "6.27.0", + "react-router-dom": "6.27.0", "runtime-env-cra": "file:lib/runtime-env-cra", "tss-react": "4.9.13" }, "devDependencies": { - "@eslint/compat": "1.1.1", + "@eslint/compat": "1.2.0", "@eslint/eslintrc": "3.1.0", - "@eslint/js": "9.11.1", + "@eslint/js": "9.12.0", "@microsoft/eslint-formatter-sarif": "3.1.0", "@trivago/prettier-plugin-sort-imports": "4.3.0", "@types/inflection": "1.13.2", "@types/jest": "29.5.13", - "@types/node": "20.16.10", + "@types/node": "20.16.11", "@types/prop-types": "15.7.13", "@types/react": "18.3.11", - "@types/react-dom": "18.3.0", + "@types/react-dom": "18.3.1", "@types/recharts": "1.8.29", - "@typescript-eslint/eslint-plugin": "8.8.0", - "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", "@vitejs/plugin-react": "4.3.2", - "eslint": "9.11.1", + "eslint": "9.12.0", "eslint-plugin-react": "7.37.1", "eslint-plugin-react-hooks": "rc", "eslint-plugin-security": "3.0.1", - "globals": "15.10.0", + "globals": "15.11.0", "prettier": "3.3.3", "rewire": "7.0.0", - "typescript": "5.6.2", + "typescript": "5.6.3", "vite": "5.4.8" } }, @@ -126,9 +126,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", - "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", "dev": true, "license": "MIT", "engines": { @@ -136,9 +136,9 @@ } }, "node_modules/@babel/core": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", - "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dev": true, "license": "MIT", "dependencies": { @@ -148,10 +148,10 @@ "@babel/helper-compilation-targets": "^7.25.7", "@babel/helper-module-transforms": "^7.25.7", "@babel/helpers": "^7.25.7", - "@babel/parser": "^7.25.7", + "@babel/parser": "^7.25.8", "@babel/template": "^7.25.7", "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7", + "@babel/types": "^7.25.8", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -392,12 +392,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", - "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -492,9 +492,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", - "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.7", @@ -1114,13 +1114,21 @@ } }, "node_modules/@eslint/compat": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz", - "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.0.tgz", + "integrity": "sha512-CkPWddN7J9JPrQedEr2X7AjK9y1jaMJtxZ4A/+jTMFA2+n5BWhcKHW/EbJyARqg2zzQfgtWUtVmG3hrG6+nGpg==", "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/config-array": { @@ -1186,9 +1194,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", - "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, "license": "MIT", "engines": { @@ -1276,6 +1284,30 @@ "react": ">=16.3" } }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1315,9 +1347,9 @@ "license": "BSD-3-Clause" }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1652,6 +1684,7 @@ "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -1987,9 +2020,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.17", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.17.tgz", - "integrity": "sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==", + "version": "7.2.18", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.18.tgz", + "integrity": "sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -2085,9 +2118,9 @@ "license": "MIT" }, "node_modules/@remix-run/router": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", - "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -2325,9 +2358,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.0.tgz", - "integrity": "sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==", + "version": "5.59.13", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.13.tgz", + "integrity": "sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ==", "license": "MIT", "funding": { "type": "github", @@ -2335,12 +2368,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.59.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.0.tgz", - "integrity": "sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==", + "version": "5.59.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.13.tgz", + "integrity": "sha512-GB2ELtiH8tL0rcFiM4sWvnXhazt1xRXX/LolMEV12kfEKu58aNA4lQoieslP61PO4vZO9JJMwm+6lqyS0E1HOA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.59.0" + "@tanstack/query-core": "5.59.13" }, "funding": { "type": "github", @@ -2931,9 +2964,9 @@ } }, "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", - "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "dev": true, "license": "MIT", "dependencies": { @@ -3139,9 +3172,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.16.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz", - "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==", + "version": "20.16.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", + "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, "license": "MIT", "dependencies": { @@ -3171,9 +3204,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3231,17 +3264,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", - "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", + "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/type-utils": "8.8.0", - "@typescript-eslint/utils": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/type-utils": "8.8.1", + "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3265,16 +3298,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz", - "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", + "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4" }, "engines": { @@ -3294,14 +3327,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", - "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", + "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0" + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3312,14 +3345,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", - "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", + "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.0", - "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.1", + "@typescript-eslint/utils": "8.8.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -3337,9 +3370,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", - "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", + "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", "dev": true, "license": "MIT", "engines": { @@ -3351,14 +3384,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", - "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", + "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/visitor-keys": "8.8.0", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/visitor-keys": "8.8.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3406,16 +3439,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", - "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", + "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.0", - "@typescript-eslint/types": "8.8.0", - "@typescript-eslint/typescript-estree": "8.8.0" + "@typescript-eslint/scope-manager": "8.8.1", + "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/typescript-estree": "8.8.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3429,13 +3462,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", - "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", + "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/types": "8.8.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -3683,9 +3716,9 @@ "license": "MIT" }, "node_modules/attr-accept": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", - "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.4.tgz", + "integrity": "sha512-2pA6xFIbdTUDCAwjN8nQwI+842VwzbDUXO2IYlpPXQIORgKnavorcr4Ce3rwh+zsNg9zK7QPsdvDj3Lum4WX4w==", "license": "MIT", "engines": { "node": ">=4" @@ -3835,9 +3868,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001668", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", + "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", "dev": true, "funding": [ { @@ -3999,9 +4032,9 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, "node_modules/convert-source-map": { @@ -4771,9 +4804,9 @@ "license": "(MPL-2.0 OR Apache-2.0)" }, "node_modules/electron-to-chromium": { - "version": "1.5.32", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz", - "integrity": "sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==", + "version": "1.5.36", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", + "integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", "dev": true, "license": "ISC" }, @@ -4887,9 +4920,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", - "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", + "integrity": "sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==", "dev": true, "license": "MIT", "dependencies": { @@ -4900,12 +4933,12 @@ "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", + "globalthis": "^1.0.4", "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", + "iterator.prototype": "^1.1.3", "safe-array-concat": "^1.1.2" }, "engines": { @@ -5028,9 +5061,9 @@ } }, "node_modules/eslint": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz", - "integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, "license": "MIT", "dependencies": { @@ -5039,11 +5072,11 @@ "@eslint/config-array": "^0.18.0", "@eslint/core": "^0.6.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.11.1", + "@eslint/js": "9.12.0", "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -5051,9 +5084,9 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5063,13 +5096,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { @@ -5124,9 +5155,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.1.0-rc-0751fac7-20241002", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-0751fac7-20241002.tgz", - "integrity": "sha512-9CEhaksLMmjRIpLdm5xazaCvDYsLZ0OKgCBQTyuiP4LvtB1wiQVBVGgQVWF1iC2FZmilFsefdmxUJb0nQl80Yg==", + "version": "5.1.0-rc-cd22717c-20241013", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-cd22717c-20241013.tgz", + "integrity": "sha512-p4gcOd9uRugj0w+23DAcb1quxz0RLLQrebiQ2wQ3+LbRlSic5SwixqdZmMsuQLkKdWYYftVIH6gsBoUBHNdsZg==", "dev": true, "license": "MIT", "engines": { @@ -5584,9 +5615,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5749,9 +5780,9 @@ } }, "node_modules/globals": { - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.10.0.tgz", - "integrity": "sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, "license": "MIT", "engines": { @@ -6418,9 +6449,9 @@ "license": "ISC" }, "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6429,6 +6460,9 @@ "has-symbols": "^1.0.3", "reflect.getprototypeof": "^1.0.4", "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/javascript-natural-sort": { @@ -7253,15 +7287,15 @@ } }, "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz", + "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==", "license": "MIT", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.12.1", "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" } }, "node_modules/ms": { @@ -7497,9 +7531,9 @@ } }, "node_modules/package-manager-detector": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz", - "integrity": "sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz", + "integrity": "sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==", "license": "MIT" }, "node_modules/parent-module": { @@ -7609,13 +7643,13 @@ } }, "node_modules/pkg-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", - "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", "license": "MIT", "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.1", + "confbox": "^0.1.8", + "mlly": "^1.7.2", "pathe": "^1.1.2" } }, @@ -7764,14 +7798,14 @@ } }, "node_modules/prosemirror-commands": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.0.tgz", - "integrity": "sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.1.tgz", + "integrity": "sha512-tNy4uaGWzvuUYXDke7B28krndIrdQJhSh0OLpubtwtEwFbjItOj/eoAfPvstBJyyV0S2+b5t4G+4XPXdxar6pg==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" + "prosemirror-transform": "^1.10.2" } }, "node_modules/prosemirror-dropcursor": { @@ -7853,9 +7887,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.3.tgz", - "integrity": "sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz", + "integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==", "license": "MIT", "dependencies": { "orderedmap": "^2.0.0" @@ -7921,9 +7955,9 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.0.tgz", - "integrity": "sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", "license": "MIT", "dependencies": { "prosemirror-model": "^1.21.0" @@ -7966,9 +8000,9 @@ } }, "node_modules/query-string": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.0.tgz", - "integrity": "sha512-t6dqMECpCkqfyv2FfwVS1xcB6lgXW/0XZSaKdsCNGYkqMO76AFiJEg4vINzoDKcZa6MS7JX+OHIjwh06K5vczw==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.1.tgz", + "integrity": "sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==", "license": "MIT", "dependencies": { "decode-uri-component": "^0.4.1", @@ -8004,9 +8038,9 @@ "license": "MIT" }, "node_modules/ra-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-5.2.2.tgz", - "integrity": "sha512-X2wZ5cg+TYDCuyO0aFAhvg+aqxb2/xQzxCuTnC3HR+9hCYXjckwjQAs3yjJwkdLLrOAS+w8/aiKMy5tZPbh95Q==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-5.2.3.tgz", + "integrity": "sha512-uaJX54igNS9VPK1jiSfwjtDaW6P70cxuZvRBBPqqNoN04fldVHUKUaC7wY/LsWnIRCPATz+TU93RaKYDUNw8FQ==", "license": "MIT", "dependencies": { "@tanstack/react-query": "^5.8.4", @@ -8075,19 +8109,19 @@ } }, "node_modules/ra-i18n-polyglot": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-5.2.2.tgz", - "integrity": "sha512-D9elwYgelAVw5cnAsNyhUHMr+Y0N0+4yDx7vSR78bxzll07C95Tw0ZruFIp5Wa4vTPmvnKpX+yiwiJ/TqU88rg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-5.2.3.tgz", + "integrity": "sha512-e7njr3BtPLdqQaqlWdjqjYWEWPZ4qca9QCVQBSxMkIZNoUf1mbKTHq+TDfnlCgnSKmADS9l8FRIcP+7fV6RNEQ==", "license": "MIT", "dependencies": { "node-polyglot": "^2.2.2", - "ra-core": "^5.2.2" + "ra-core": "^5.2.3" } }, "node_modules/ra-input-rich-text": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ra-input-rich-text/-/ra-input-rich-text-5.2.2.tgz", - "integrity": "sha512-q54bwse0zLmRnIM9v/cQeiojpaAxhHwOmGtpaTsUiz0NTWHyGepI080fG68DrYHXJGoOXZ03PJHQUUQaDuqBig==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ra-input-rich-text/-/ra-input-rich-text-5.2.3.tgz", + "integrity": "sha512-TtImIvhLZDIqnyijkKqZA13QYkxorEANjvu2xwb4W0V8/VR8shjadscHWEmqNyps6nFnU22mjgZDQKOOvXzPow==", "license": "MIT", "dependencies": { "@tiptap/core": "^2.0.3", @@ -8114,18 +8148,18 @@ } }, "node_modules/ra-language-english": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-5.2.2.tgz", - "integrity": "sha512-dbCAv5ELaCWmh2raf6VIWWuQK3Z+IQ3jrlatgdipjTse57I0zu7Rp5WwlOdYfWRk6pA3WsMvHHrkuZNEWroDyg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-5.2.3.tgz", + "integrity": "sha512-MkY5kWUtAmQVwvGA674UcY1X7cops8Bmk8PshHIGFlhNNJR4rlrNcl1n4Ypt9M18SbGWhltYvjYMpcILdtKdjQ==", "license": "MIT", "dependencies": { - "ra-core": "^5.2.2" + "ra-core": "^5.2.3" } }, "node_modules/ra-ui-materialui": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-5.2.2.tgz", - "integrity": "sha512-RpMKa9C+VLeH8fDLHJ4jVZv6eYPsqBqO+D3NpC6vYI9dkFfX62E74UqVEOI98/IADURNxeG2cuSVBrwS+gtZKg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-5.2.3.tgz", + "integrity": "sha512-H2KOBzbjTv81iryRL8V17cQHihWVAzRkO2xa1D4+SmjJRAtYPrel46BqtOgHbgT3/3TeuMNIbCPF9sWm7JlRvg==", "license": "MIT", "dependencies": { "@tanstack/react-query": "^5.8.4", @@ -8145,6 +8179,7 @@ "peerDependencies": { "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", + "@mui/utils": "^5.15.20", "ra-core": "^5.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", @@ -8218,19 +8253,19 @@ } }, "node_modules/react-admin": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-5.2.2.tgz", - "integrity": "sha512-fYsfLAGNo7wf3zlLUS2k+/3uxPdz68lwuyAGLnnHjHR55e0uGvj1m6BrM/ma/YhTmUjpvetNLbWBQj8GZWWjrw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-5.2.3.tgz", + "integrity": "sha512-rUtk86O1SCIlXkTfLYrzSyJe2xIqanI+7R/xRkWX2Aodw3OGxMBsdzyScqa9Bn9CbyhDSVlZHVCSHNf9a5PWlA==", "license": "MIT", "dependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", - "ra-core": "^5.2.2", - "ra-i18n-polyglot": "^5.2.2", - "ra-language-english": "^5.2.2", - "ra-ui-materialui": "^5.2.2", + "ra-core": "^5.2.3", + "ra-i18n-polyglot": "^5.2.3", + "ra-language-english": "^5.2.3", + "ra-ui-materialui": "^5.2.3", "react-hook-form": "^7.53.0", "react-router": "^6.22.0", "react-router-dom": "^6.22.0" @@ -8315,15 +8350,15 @@ "license": "MIT" }, "node_modules/react-oidc-context": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.1.1.tgz", - "integrity": "sha512-pXZYVUVOU+wnLFZVh5HFGiAyAHpy0mm7mZBVp5oNuCln/7yd+Uhb7KfYI2QN+LLQI0kIv6FHIcqeUFjMIsM5gA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-3.2.0.tgz", + "integrity": "sha512-ZLaCRLWV84Cn9pFdsatmblqxLMv0np69GWVXq9RWGqAjppdOGXNIbIxWMByIio0oSCVUwdeqwYRnJme0tjqd8A==", "license": "MIT", "engines": { "node": ">=18" }, "peerDependencies": { - "oidc-client-ts": "^3.0.0", + "oidc-client-ts": "^3.1.0", "react": ">=16.8.0" } }, @@ -8338,12 +8373,12 @@ } }, "node_modules/react-router": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", - "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.2" + "@remix-run/router": "1.20.0" }, "engines": { "node": ">=14.0.0" @@ -8353,13 +8388,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", - "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.19.2", - "react-router": "6.26.2" + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" }, "engines": { "node": ">=14.0.0" @@ -8608,6 +8643,7 @@ "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -9462,9 +9498,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/frontend/package.json b/frontend/package.json index cd852d7bd..b18cd087b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "secobserve", - "version": "1.19.0", + "version": "1.20.0", "license": "BSD-3-Clause", "description": "SecObserve is an open source vulnerability management system for software development and cloud environments.", "private": true, @@ -20,18 +20,18 @@ "mermaid": "11.3.0", "oidc-client-ts": "3.1.0", "prop-types": "15.8.1", - "query-string": "9.1.0", - "ra-i18n-polyglot": "5.2.2", - "ra-input-rich-text": "5.2.2", - "ra-language-english": "5.2.2", + "query-string": "9.1.1", + "ra-i18n-polyglot": "5.2.3", + "ra-input-rich-text": "5.2.3", + "ra-language-english": "5.2.3", "react": "18.3.1", - "react-admin": "5.2.2", + "react-admin": "5.2.3", "react-chartjs-2": "5.2.0", "react-dom": "18.3.1", "react-hook-form": "7.53.0", - "react-oidc-context": "3.1.1", - "react-router": "6.26.2", - "react-router-dom": "6.26.2", + "react-oidc-context": "3.2.0", + "react-router": "6.27.0", + "react-router-dom": "6.27.0", "runtime-env-cra": "file:lib/runtime-env-cra", "tss-react": "4.9.13" }, @@ -49,29 +49,29 @@ "not op_mini all" ], "devDependencies": { - "@eslint/compat": "1.1.1", + "@eslint/compat": "1.2.0", "@eslint/eslintrc": "3.1.0", - "@eslint/js": "9.11.1", + "@eslint/js": "9.12.0", "@microsoft/eslint-formatter-sarif": "3.1.0", "@trivago/prettier-plugin-sort-imports": "4.3.0", "@types/inflection": "1.13.2", "@types/jest": "29.5.13", - "@types/node": "20.16.10", + "@types/node": "20.16.11", "@types/prop-types": "15.7.13", "@types/react": "18.3.11", - "@types/react-dom": "18.3.0", + "@types/react-dom": "18.3.1", "@types/recharts": "1.8.29", - "@typescript-eslint/eslint-plugin": "8.8.0", - "@typescript-eslint/parser": "8.8.0", + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", "@vitejs/plugin-react": "4.3.2", - "eslint": "9.11.1", + "eslint": "9.12.0", "eslint-plugin-react": "7.37.1", "eslint-plugin-react-hooks": "rc", "eslint-plugin-security": "3.0.1", - "globals": "15.10.0", + "globals": "15.11.0", "prettier": "3.3.3", "rewire": "7.0.0", - "typescript": "5.6.2", + "typescript": "5.6.3", "vite": "5.4.8" } } diff --git a/frontend/src/access_control/access_control_administration/AccessControlAdministration.tsx b/frontend/src/access_control/access_control_administration/AccessControlAdministration.tsx index 2b7202816..98c355542 100644 --- a/frontend/src/access_control/access_control_administration/AccessControlAdministration.tsx +++ b/frontend/src/access_control/access_control_administration/AccessControlAdministration.tsx @@ -113,7 +113,7 @@ export default function AccessControlAdministration() { - + diff --git a/frontend/src/access_control/authorization_groups/AuthorizationGroupUserAdd.tsx b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberAdd.tsx similarity index 84% rename from frontend/src/access_control/authorization_groups/AuthorizationGroupUserAdd.tsx rename to frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberAdd.tsx index a0ed6eb28..330cf66df 100644 --- a/frontend/src/access_control/authorization_groups/AuthorizationGroupUserAdd.tsx +++ b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberAdd.tsx @@ -2,17 +2,26 @@ import AddIcon from "@mui/icons-material/Add"; import CancelIcon from "@mui/icons-material/Cancel"; import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material"; import { Fragment, useState } from "react"; -import { CreateBase, ReferenceInput, SaveButton, SimpleForm, Toolbar, useNotify, useRefresh } from "react-admin"; +import { + BooleanInput, + CreateBase, + ReferenceInput, + SaveButton, + SimpleForm, + Toolbar, + useNotify, + useRefresh, +} from "react-admin"; import { validate_required } from "../../commons/custom_validators"; import { AutocompleteInputWide } from "../../commons/layout/themes"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; -export type AuthorizationGroupUserAddProps = { +export type AuthorizationGroupMemberAddProps = { id: any; }; -const AuthorizationGroupUserAdd = ({ id }: AuthorizationGroupUserAddProps) => { +const AuthorizationGroupMemberAdd = ({ id }: AuthorizationGroupMemberAddProps) => { const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); @@ -47,10 +56,11 @@ const AuthorizationGroupUserAdd = ({ id }: AuthorizationGroupUserAddProps) => { ); const add_user = (data: any) => { - const url = window.__RUNTIME_CONFIG__.API_BASE_URL + "/authorization_groups/" + id + "/add_user/"; + const url = window.__RUNTIME_CONFIG__.API_BASE_URL + "/authorization_group_members/"; + const body = JSON.stringify({ authorization_group: id, ...data }); httpClient(url, { method: "POST", - body: JSON.stringify(data), + body: body, }) .then(() => { refresh(); @@ -86,6 +96,7 @@ const AuthorizationGroupUserAdd = ({ id }: AuthorizationGroupUserAddProps) => { > + @@ -94,4 +105,4 @@ const AuthorizationGroupUserAdd = ({ id }: AuthorizationGroupUserAddProps) => { ); }; -export default AuthorizationGroupUserAdd; +export default AuthorizationGroupMemberAdd; diff --git a/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEdit.tsx b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEdit.tsx new file mode 100644 index 000000000..5b0f82b66 --- /dev/null +++ b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEdit.tsx @@ -0,0 +1,96 @@ +import CancelIcon from "@mui/icons-material/Cancel"; +import EditIcon from "@mui/icons-material/Edit"; +import { Button, Dialog, DialogContent, DialogTitle } from "@mui/material"; +import { Fragment, useState } from "react"; +import { BooleanInput, SaveButton, SimpleForm, Toolbar, useNotify, useRefresh, useUpdate } from "react-admin"; + +import { TextInputWide } from "../../commons/layout/themes"; + +const ProductMemberEdit = () => { + const [open, setOpen] = useState(false); + const [update] = useUpdate(); + const refresh = useRefresh(); + const notify = useNotify(); + const handleOpen = () => setOpen(true); + const handleCancel = () => setOpen(false); + const handleClose = (event: object, reason: string) => { + if (reason && reason == "backdropClick") return; + setOpen(false); + }; + + const member_update = async (data: any) => { + const patch = { + is_manager: data.is_manager, + }; + + update( + "authorization_group_members", + + { + id: data.id, + data: patch, + }, + { + onSuccess: () => { + refresh(); + notify("User member updated", { + type: "success", + }); + }, + onError: (error: any) => { + notify(error.message, { + type: "warning", + }); + }, + } + ); + setOpen(false); + }; + + const CancelButton = () => ( + + ); + + const CustomToolbar = () => ( + + + + + ); + return ( + + + + Edit user + + }> + + + + + + + ); +}; + +export default ProductMemberEdit; diff --git a/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEmbeddedList.tsx b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEmbeddedList.tsx new file mode 100644 index 000000000..5f7708e56 --- /dev/null +++ b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberEmbeddedList.tsx @@ -0,0 +1,100 @@ +import { Stack } from "@mui/material"; +import { + BooleanField, + Datagrid, + FilterForm, + Identifier, + ListContextProvider, + NullableBooleanInput, + TextInput, + WithRecord, + useListController, +} from "react-admin"; + +import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; +import TextUrlField from "../../commons/custom_fields/TextUrlField"; +import { is_superuser } from "../../commons/functions"; +import { getSettingListSize } from "../../commons/user_settings/functions"; +import AuthorizationGroupMemberAdd from "./AuthorizationGroupMemberAdd"; +import AuthorizationGroupMemberEdit from "./AuthorizationGroupMemberEdit"; +import AuthorizationGroupMemberRemove from "./AuthorizationGroupMemberRemove"; + +function listFilters() { + return [ + , + , + , + ]; +} + +const showUser = (id: Identifier) => { + return "#/users/" + id + "/show"; +}; + +type AuthorizationGroupMemberEmbeddedListProps = { + authorization_group: any; +}; + +const AuthorizationGroupMemberEmbeddedList = ({ authorization_group }: AuthorizationGroupMemberEmbeddedListProps) => { + const listContext = useListController({ + filter: { authorization_group: Number(authorization_group.id) }, + perPage: 25, + resource: "authorization_group_members", + sort: { field: "user_data.full_name", order: "ASC" }, + filterDefaultValues: {}, + disableSyncWithLocation: false, + }); + + if (listContext.isLoading) { + return
Loading...
; + } + + return ( + +
+ {(is_superuser() || authorization_group.is_manager) && ( + + )} + + + ( + + )} + /> + ( + + )} + /> + + {(is_superuser() || authorization_group.is_manager) && ( + ( + + + + + )} + /> + )} + + +
+
+ ); +}; + +export default AuthorizationGroupMemberEmbeddedList; diff --git a/frontend/src/access_control/authorization_groups/AuthorizationGroupUserRemove.tsx b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberRemove.tsx similarity index 64% rename from frontend/src/access_control/authorization_groups/AuthorizationGroupUserRemove.tsx rename to frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberRemove.tsx index 3abb67429..623074200 100644 --- a/frontend/src/access_control/authorization_groups/AuthorizationGroupUserRemove.tsx +++ b/frontend/src/access_control/authorization_group_members/AuthorizationGroupMemberRemove.tsx @@ -4,12 +4,11 @@ import { Button, Confirm, useNotify, useRefresh } from "react-admin"; import { httpClient } from "../../commons/ra-data-django-rest-framework"; -type AuthorizationGroupUserRemoveProps = { - id: any; - user: any; +type AuthorizationGroupMemberRemoveProps = { + authorization_group_member: any; }; -const AuthorizationGroupUserRemove = ({ id, user }: AuthorizationGroupUserRemoveProps) => { +const AuthorizationGroupMemberRemove = ({ authorization_group_member }: AuthorizationGroupMemberRemoveProps) => { const [open, setOpen] = useState(false); const refresh = useRefresh(); const notify = useNotify(); @@ -17,11 +16,13 @@ const AuthorizationGroupUserRemove = ({ id, user }: AuthorizationGroupUserRemove const handleDialogClose = () => setOpen(false); const removeUser = async () => { - const url = window.__RUNTIME_CONFIG__.API_BASE_URL + "/authorization_groups/" + id + "/remove_user/"; - const data = { user: user.id }; + const url = + window.__RUNTIME_CONFIG__.API_BASE_URL + + "/authorization_group_members/" + + authorization_group_member.id + + "/"; httpClient(url, { - method: "POST", - body: JSON.stringify(data), + method: "DELETE", }) .then(() => { refresh(); @@ -40,7 +41,9 @@ const AuthorizationGroupUserRemove = ({ id, user }: AuthorizationGroupUserRemove @@ -48,4 +51,4 @@ const AuthorizationGroupUserRemove = ({ id, user }: AuthorizationGroupUserRemove ); }; -export default AuthorizationGroupUserRemove; +export default AuthorizationGroupMemberRemove; diff --git a/frontend/src/access_control/authorization_groups/AuthorizationGroupEmbeddedList.tsx b/frontend/src/access_control/authorization_groups/AuthorizationGroupEmbeddedList.tsx index daea45a96..81afff920 100644 --- a/frontend/src/access_control/authorization_groups/AuthorizationGroupEmbeddedList.tsx +++ b/frontend/src/access_control/authorization_groups/AuthorizationGroupEmbeddedList.tsx @@ -1,7 +1,7 @@ import { Datagrid, FilterForm, ListContextProvider, TextField, TextInput, useListController } from "react-admin"; import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; -import { is_superuser } from "../../commons/functions"; +import { is_external } from "../../commons/functions"; import { getSettingListSize } from "../../commons/user_settings/functions"; import AuthorizationGroupCreateButton from "./AuthorizationGroupCreateButton"; @@ -13,15 +13,22 @@ function listFilters() { return [, ]; } -const AuthorizationGroupEmbeddedList = () => { +type AuthorizationGroupEmbeddedListProps = { + user: any; +}; + +const AuthorizationGroupEmbeddedList = ({ user }: AuthorizationGroupEmbeddedListProps) => { + const filter = user ? { user: Number(user.id) } : {}; + const storeKey = user ? false : "authorization_groups.embedded"; + const listContext = useListController({ - filter: {}, + filter: filter, perPage: 25, resource: "authorization_groups", sort: { field: "name", order: "ASC" }, filterDefaultValues: { is_active: true }, disableSyncWithLocation: false, - storeKey: "authorization_groups.embedded", + storeKey: storeKey, }); if (listContext.isLoading) { @@ -31,8 +38,8 @@ const AuthorizationGroupEmbeddedList = () => { return (
- {is_superuser() && } - + {!is_external() && !user && } + {!user && } { + const authorization_group = useRecordContext(); return ( @@ -16,7 +26,7 @@ const ShowActions = () => { filterDefaultValues={{ is_active: true }} storeKey="authorization_groups.embedded" /> - {is_superuser() && } + {authorization_group && authorization_group.is_manager && } ); @@ -29,7 +39,7 @@ const AuthorizationGroupComponent = () => { ( - + Authorization Group @@ -44,12 +54,34 @@ const AuthorizationGroupComponent = () => { )} - + Users - + + {authorization_group.has_product_group_members && ( + + + Product Groups + + + + )} + {authorization_group.has_product_members && ( + + + Products + + + + )} )} /> diff --git a/frontend/src/access_control/users/UserAGEmbeddedList.tsx b/frontend/src/access_control/users/UserAGEmbeddedList.tsx deleted file mode 100644 index 82d21e233..000000000 --- a/frontend/src/access_control/users/UserAGEmbeddedList.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { - BooleanField, - Datagrid, - FilterForm, - ListContextProvider, - NullableBooleanInput, - TextField, - TextInput, - WithRecord, - useListController, -} from "react-admin"; - -import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; -import { is_superuser } from "../../commons/functions"; -import { getSettingListSize } from "../../commons/user_settings/functions"; -import AuthorizationGroupUserAdd from "../authorization_groups/AuthorizationGroupUserAdd"; -import AuthorizationGroupUserRemove from "../authorization_groups/AuthorizationGroupUserRemove"; - -function listFilters() { - if (is_superuser()) { - return [ - , - , - , - , - , - , - ]; - } else { - return [, ]; - } -} - -type UserAGEmbeddedListProps = { - authorization_group: any; -}; - -const UserAGEmbeddedList = ({ authorization_group }: UserAGEmbeddedListProps) => { - const listContext = useListController({ - filter: { authorization_group: Number(authorization_group.id) }, - perPage: 25, - resource: "users", - sort: { field: "username", order: "ASC" }, - filterDefaultValues: { is_active: true }, - disableSyncWithLocation: false, - storeKey: "users.agembedded", - }); - - if (listContext.isLoading) { - return
Loading...
; - } - - localStorage.setItem("useragembeddedlist", "true"); - localStorage.removeItem("userembeddedlist"); - localStorage.setItem("useragembeddedlist.authorization_group", authorization_group.id); - - return ( - -
- {is_superuser() && } - - - - - {is_superuser() && } - {is_superuser() && } - {is_superuser() && } - {is_superuser() && } - {is_superuser() && ( - } - /> - )} - - -
-
- ); -}; - -export default UserAGEmbeddedList; diff --git a/frontend/src/access_control/users/UserShow.tsx b/frontend/src/access_control/users/UserShow.tsx index 1d4d2c7c6..e2ddc46de 100644 --- a/frontend/src/access_control/users/UserShow.tsx +++ b/frontend/src/access_control/users/UserShow.tsx @@ -1,9 +1,7 @@ import { Box, Paper, Stack, Typography } from "@mui/material"; import { Fragment } from "react"; import { - ArrayField, BooleanField, - Datagrid, DateField, EditButton, Labeled, @@ -17,6 +15,8 @@ import { import { is_superuser } from "../../commons/functions"; import { useStyles } from "../../commons/layout/themes"; +import UserProductMemberEmbeddedList from "../../core/product_members/UserProductMemberEmbeddedList"; +import AuthorizationGroupEmbeddedList from "../authorization_groups/AuthorizationGroupEmbeddedList"; import UserChangePassword from "./UserChangePassword"; const ShowActions = () => { @@ -58,109 +58,128 @@ const ShowActions = () => { ); }; -const ShowAuthorizationGroup = (id: any) => { - return "../../../../authorization_groups/" + id + "/show"; -}; - const UserComponent = () => { const { classes } = useStyles(); const current_user = localStorage.getItem("user"); + const showFullInformation = (user: any) => { + return is_superuser() || (current_user && JSON.parse(current_user).id == user.id); + }; + + const userWidth = (user: any) => { + return showFullInformation(user) ? "50%" : "100%"; + }; + return ( ( - - - User - - - - - - - - - {user.first_name && ( - - - - )} - {user.last_name && ( - - - - )} - {user.email && ( - - - - )} - {user.date_joined && ( - - - - )} - {user.has_password != undefined && ( - - - - )} - {user.is_oidc_user != undefined && ( - - - - )} + + + + + User + + + + + + + + + {user.first_name && ( + + + + )} + {user.last_name && ( + + + + )} + {user.email && ( + + + + )} + {user.date_joined && ( + + + + )} + {user.has_password != undefined && ( + + + + )} + {user.is_oidc_user != undefined && ( + + + + )} + + - - {(is_superuser() || (current_user && JSON.parse(current_user).id == user.id)) && ( - + {showFullInformation(user) && ( + + + + Permissions + + + + + + + + + + + + + + + + Settings + + + {user.setting_theme && ( + + + + )} + {user.setting_list_size && ( + + + + )} + + + + )} + + {showFullInformation(user) && user.has_authorization_groups && ( + - Permissions + Authorization Groups - - - - - - - - - - - + )} - {(is_superuser() || (current_user && JSON.parse(current_user).id == user.id)) && ( - + {showFullInformation(user) && user.has_product_group_members && ( + - Settings + Product Groups - - {user.setting_theme && ( - - - - )} - {user.setting_list_size && ( - - - - )} - + )} - {(is_superuser() || (current_user && JSON.parse(current_user).id == user.id)) && ( + {showFullInformation(user) && user.has_product_members && ( - Groups + Products - - - - - {" "} - + )} diff --git a/frontend/src/commons/custom_validators.ts b/frontend/src/commons/custom_validators.ts index 9314d96e5..60e2c15ac 100644 --- a/frontend/src/commons/custom_validators.ts +++ b/frontend/src/commons/custom_validators.ts @@ -16,6 +16,7 @@ export const validate_0_10 = [minValue(0), maxValue(10)]; export const validate_0_999999 = [minValue(0), maxValue(999999)]; export const validate_0_23 = [minValue(0), maxValue(23)]; export const validate_0_59 = [minValue(0), maxValue(59)]; +export const validate_1_4096 = [minValue(1), maxValue(4096)]; export const validate_2000_9999 = [minValue(2000), maxValue(9999)]; export function validate_after_today() { diff --git a/frontend/src/commons/functions.tsx b/frontend/src/commons/functions.tsx index e9c791f30..252e33200 100644 --- a/frontend/src/commons/functions.tsx +++ b/frontend/src/commons/functions.tsx @@ -173,3 +173,8 @@ export const is_superuser = () => { const user = localStorage.getItem("user"); return user && JSON.parse(user).is_superuser; }; + +export const is_external = () => { + const user = localStorage.getItem("user"); + return user && JSON.parse(user).is_external; +}; diff --git a/frontend/src/commons/layout/AppBar.tsx b/frontend/src/commons/layout/AppBar.tsx index b89f7313e..5d89c0d90 100644 --- a/frontend/src/commons/layout/AppBar.tsx +++ b/frontend/src/commons/layout/AppBar.tsx @@ -8,6 +8,7 @@ import { Link } from "react-router-dom"; import About from "../about/About"; import Logo from "./Logo"; +import ToggleThemeButton from "./ToggleThemeButton"; const DocumentationMenu = forwardRef(() => { const userMenuContext = useUserMenu(); @@ -108,6 +109,7 @@ const CustomAppBar = () => { userMenu={} toolbar={ <> + } diff --git a/frontend/src/commons/layout/ToggleThemeButton.tsx b/frontend/src/commons/layout/ToggleThemeButton.tsx new file mode 100644 index 000000000..bcf2801f9 --- /dev/null +++ b/frontend/src/commons/layout/ToggleThemeButton.tsx @@ -0,0 +1,47 @@ +import DarkModeIcon from "@mui/icons-material/DarkMode"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import { IconButton, Tooltip } from "@mui/material"; +import { useTheme } from "react-admin"; + +import { getSettingTheme, saveSettingTheme } from "../../commons/user_settings/functions"; + +const tooltipTitle = () => { + const theme = getSettingTheme(); + return theme === "dark" ? "Light mode" : "Dark mode"; +}; + +const ThemeIcon = () => { + const theme = getSettingTheme(); + return theme === "dark" ? ( + + ) : ( + + ); +}; + +const ToggleThemeButton = () => { + const [, setTheme] = useTheme(); + + const toogleTheme = () => { + const theme = getSettingTheme(); + if (theme === "dark") { + setTheme("light"); + localStorage.setItem("theme", "light"); + saveSettingTheme("light"); + } else { + setTheme("dark"); + localStorage.setItem("theme", "dark"); + saveSettingTheme("dark"); + } + }; + + return ( + + + + + + ); +}; + +export default ToggleThemeButton; diff --git a/frontend/src/commons/settings/SettingsEdit.tsx b/frontend/src/commons/settings/SettingsEdit.tsx index 666e12a25..9d660d8e5 100644 --- a/frontend/src/commons/settings/SettingsEdit.tsx +++ b/frontend/src/commons/settings/SettingsEdit.tsx @@ -3,7 +3,13 @@ import { Fragment } from "react"; import { BooleanInput, Edit, FormDataConsumer, NumberInput, SaveButton, SimpleForm, Toolbar } from "react-admin"; import settings from "."; -import { validate_0_23, validate_0_59, validate_0_999999, validate_255 } from "../../commons/custom_validators"; +import { + validate_0_23, + validate_0_59, + validate_0_999999, + validate_1_4096, + validate_255, +} from "../../commons/custom_validators"; import ListHeader from "../../commons/layout/ListHeader"; import { TextInputWide } from "../../commons/layout/themes"; @@ -312,7 +318,7 @@ const SettingsEdit = () => { helperText="Days before risk acceptance expires, 0 means no expiry" sx={{ marginBottom: 2 }} /> - + { step={1} validate={validate_0_23} helperText="Hour crontab expression for checking risk acceptance expiry (UTC)" - sx={{ marginBottom: 2 }} /> { step={1} validate={validate_0_59} helperText="Minute crontab expression for checking risk acceptance expiry" - sx={{ marginBottom: 2 }} /> - + Automatic API import - + {({ formData }) => formData.feature_automatic_api_import && ( - + { step={1} validate={validate_0_23} helperText="Hour crontab expression for API imports (UTC)" - sx={{ marginBottom: 2 }} /> { step={1} validate={validate_0_59} helperText="Minute crontab expression for API imports" - sx={{ marginBottom: 2 }} /> ) } + + + + Password validation for non-OIDC users + + + + + diff --git a/frontend/src/commons/settings/SettingsShow.tsx b/frontend/src/commons/settings/SettingsShow.tsx index 97a4e70b6..af9b0c192 100644 --- a/frontend/src/commons/settings/SettingsShow.tsx +++ b/frontend/src/commons/settings/SettingsShow.tsx @@ -196,6 +196,24 @@ const SettingsShowComponent = () => { )} + + + Password validation for non-OIDC users + + + + + + + + + + + + + + + )} /> diff --git a/frontend/src/core/observations/Mermaid_Dependencies.tsx b/frontend/src/core/observations/Mermaid_Dependencies.tsx index 1fe829100..c9968c1bc 100644 --- a/frontend/src/core/observations/Mermaid_Dependencies.tsx +++ b/frontend/src/core/observations/Mermaid_Dependencies.tsx @@ -1,5 +1,7 @@ +import AddIcon from "@mui/icons-material/Add"; import CloseIcon from "@mui/icons-material/Close"; -import { Dialog, DialogContent, DialogTitle, Divider, IconButton, Stack } from "@mui/material"; +import RemoveIcon from "@mui/icons-material/Remove"; +import { Dialog, DialogContent, DialogTitle, Divider, IconButton, Paper, Stack } from "@mui/material"; import mermaid from "mermaid"; import { Fragment, useEffect, useState } from "react"; import { Labeled, WrapperField, useRecordContext } from "react-admin"; @@ -13,6 +15,12 @@ mermaid.initialize({ }, }); +function resizeDependencyGraph(scale: number) { + const img = document.getElementById("dependency-graph-svg-in-dialog") as HTMLImageElement; + img.setAttribute("width", `${img.width * scale}`); + img.setAttribute("height", `${img.height * scale}`); +} + const GraphSVG = () => { const svg = document.querySelector(".mermaid svg"); if (svg == null) { @@ -22,7 +30,7 @@ const GraphSVG = () => { const blob = new Blob([svgData], { type: "image/svg+xml" }); const url = URL.createObjectURL(blob); console.log(url); - return Component dependency graph not available; + return Component dependency graph not available; }; const createMermaidGraph = (dependencies_str: string) => { @@ -132,6 +140,15 @@ const MermaidDependencies = () => { + + resizeDependencyGraph(1.1)}> + + + + resizeDependencyGraph(0.9)}> + + + diff --git a/frontend/src/core/observations/ObservationShow.tsx b/frontend/src/core/observations/ObservationShow.tsx index f8b247800..7a1e11685 100644 --- a/frontend/src/core/observations/ObservationShow.tsx +++ b/frontend/src/core/observations/ObservationShow.tsx @@ -108,7 +108,9 @@ const ObservationShowComponent = () => { render={(observation) => ( - Observation + + Observation + diff --git a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberDelete.tsx b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberDelete.tsx index f36823f13..45c5a0d1e 100644 --- a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberDelete.tsx +++ b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberDelete.tsx @@ -43,7 +43,7 @@ const ProductAuthorizationGroupMemberDelete = (props: ProductAuthorizationGroupM title="Delete authorization group member" content={ "Are you sure you want to delete the authorization group member " + - props.product_authorization_group_member.authorization_group_name + + props.product_authorization_group_member.authorization_group_data.name + "?" } onConfirm={handleConfirm} diff --git a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEdit.tsx b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEdit.tsx index caa394cae..912e2f9c9 100644 --- a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEdit.tsx +++ b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEdit.tsx @@ -85,7 +85,7 @@ const ProductAuthorizationGroupMemberEdit = () => { Edit authorization group member }> - + diff --git a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEmbeddedList.tsx b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEmbeddedList.tsx index 394aca2b4..1b38939f7 100644 --- a/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEmbeddedList.tsx +++ b/frontend/src/core/product_authorization_group_members/ProductAuthorizationGroupMemberEmbeddedList.tsx @@ -1,5 +1,5 @@ import { Stack } from "@mui/material"; -import { Datagrid, ListContextProvider, SelectField, TextField, WithRecord, useListController } from "react-admin"; +import { Datagrid, Identifier, ListContextProvider, SelectField, WithRecord, useListController } from "react-admin"; import { PERMISSION_PRODUCT_AUTHORIZATION_GROUP_MEMBER_DELETE, @@ -7,6 +7,7 @@ import { ROLE_CHOICES, } from "../../access_control/types"; import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; +import TextUrlField from "../../commons/custom_fields/TextUrlField"; import { getSettingListSize } from "../../commons/user_settings/functions"; import ProductAuthorizationGroupMemberDelete from "./ProductAuthorizationGroupMemberDelete"; import ProductAuthorizationGroupMemberEdit from "./ProductAuthorizationGroupMemberEdit"; @@ -15,14 +16,17 @@ type ProductAuthorizationGroupMemberEmbeddedListProps = { product: any; }; +const showAuthorizationGroup = (id: Identifier) => { + return "#/authorization_groups/" + id + "/show"; +}; + const ProductAuthorizationGroupMemberEmbeddedList = ({ product }: ProductAuthorizationGroupMemberEmbeddedListProps) => { const listContext = useListController({ filter: { product: Number(product.id) }, perPage: 25, resource: "product_authorization_group_members", - sort: { field: "authorization_group_name", order: "ASC" }, + sort: { field: "authorization_group_data.name", order: "ASC" }, disableSyncWithLocation: true, - storeKey: "product_authorization_group_member.embedded", }); if (listContext.isLoading) { @@ -39,7 +43,18 @@ const ProductAuthorizationGroupMemberEmbeddedList = ({ product }: ProductAuthori rowClick={false} resource="product_authorization_group_members" > - + ( + + )} + /> ( diff --git a/frontend/src/core/product_authorization_group_members/UserProductAuthorizationGroupMemberEmbeddedList.tsx b/frontend/src/core/product_authorization_group_members/UserProductAuthorizationGroupMemberEmbeddedList.tsx new file mode 100644 index 000000000..c11365f6a --- /dev/null +++ b/frontend/src/core/product_authorization_group_members/UserProductAuthorizationGroupMemberEmbeddedList.tsx @@ -0,0 +1,66 @@ +import { + Datagrid, + Identifier, + ListContextProvider, + RaRecord, + SelectField, + TextField, + useListController, +} from "react-admin"; + +import { ROLE_CHOICES } from "../../access_control/types"; +import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; +import { getSettingListSize } from "../../commons/user_settings/functions"; + +type UserProductAuthorizationGroupMemberEmbeddedListProps = { + authorization_group: any; + is_product_group: boolean; +}; + +function productLabel(is_product_group: boolean): string { + return is_product_group ? "Product Group" : "Product"; +} + +const showProduct = (id: Identifier, resource: string, record: RaRecord) => { + if (record.product_data.is_product_group) { + return "../../../../product_groups/" + record.product_data.id + "/show/members"; + } + return "../../../../products/" + record.product_data.id + "/show/members"; +}; + +const UserProductAuthorizationGroupMemberEmbeddedList = ({ + authorization_group, + is_product_group, +}: UserProductAuthorizationGroupMemberEmbeddedListProps) => { + const listContext = useListController({ + filter: { authorization_group: Number(authorization_group.id), is_product_group: is_product_group }, + perPage: 25, + resource: "product_authorization_group_members", + sort: { field: "product_data.name", order: "ASC" }, + disableSyncWithLocation: true, + }); + + if (listContext.isLoading) { + return
Loading...
; + } + + return ( + +
+ + + + + +
+
+ ); +}; + +export default UserProductAuthorizationGroupMemberEmbeddedList; diff --git a/frontend/src/core/product_groups/ProductGroupHeader.tsx b/frontend/src/core/product_groups/ProductGroupHeader.tsx index a8c7c520c..5d1beb085 100644 --- a/frontend/src/core/product_groups/ProductGroupHeader.tsx +++ b/frontend/src/core/product_groups/ProductGroupHeader.tsx @@ -1,4 +1,4 @@ -import { Paper } from "@mui/material"; +import { Box, Paper, Typography } from "@mui/material"; import { Labeled, RecordContextProvider, TextField, useGetOne } from "react-admin"; import { useParams } from "react-router-dom"; @@ -15,19 +15,27 @@ const ProductGroupHeader = () => { - - - - - - + + Product Group + + + + + + + + + ); diff --git a/frontend/src/core/product_members/ProductMemberEdit.tsx b/frontend/src/core/product_members/ProductMemberEdit.tsx index 477223ede..f237fd9e4 100644 --- a/frontend/src/core/product_members/ProductMemberEdit.tsx +++ b/frontend/src/core/product_members/ProductMemberEdit.tsx @@ -19,6 +19,7 @@ const ProductMemberEdit = () => { if (reason && reason == "backdropClick") return; setOpen(false); }; + const product_member_update = async (data: any) => { const patch = { role: data.role, diff --git a/frontend/src/core/product_members/ProductMemberEmbeddedList.tsx b/frontend/src/core/product_members/ProductMemberEmbeddedList.tsx index 4baf6e0ec..af43321c8 100644 --- a/frontend/src/core/product_members/ProductMemberEmbeddedList.tsx +++ b/frontend/src/core/product_members/ProductMemberEmbeddedList.tsx @@ -1,5 +1,5 @@ import { Stack } from "@mui/material"; -import { Datagrid, ListContextProvider, SelectField, TextField, WithRecord, useListController } from "react-admin"; +import { Datagrid, Identifier, ListContextProvider, SelectField, WithRecord, useListController } from "react-admin"; import { PERMISSION_PRODUCT_MEMBER_DELETE, @@ -7,6 +7,7 @@ import { ROLE_CHOICES, } from "../../access_control/types"; import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; +import TextUrlField from "../../commons/custom_fields/TextUrlField"; import { getSettingListSize } from "../../commons/user_settings/functions"; import ProductMemberDelete from "./ProductMemberDelete"; import ProductMemberEdit from "./ProductMemberEdit"; @@ -15,6 +16,10 @@ type ProductMemberEmbeddedListProps = { product: any; }; +const showUser = (id: Identifier) => { + return "#/users/" + id + "/show"; +}; + const ProductMemberEmbeddedList = ({ product }: ProductMemberEmbeddedListProps) => { const listContext = useListController({ filter: { product: Number(product.id) }, @@ -22,7 +27,6 @@ const ProductMemberEmbeddedList = ({ product }: ProductMemberEmbeddedListProps) resource: "product_members", sort: { field: "user_data.full_name", order: "ASC" }, disableSyncWithLocation: true, - storeKey: "product_member.embedded", }); if (listContext.isLoading) { @@ -39,7 +43,16 @@ const ProductMemberEmbeddedList = ({ product }: ProductMemberEmbeddedListProps) rowClick={false} resource="product_members" > - + ( + + )} + /> ( diff --git a/frontend/src/core/product_members/UserProductMemberEmbeddedList.tsx b/frontend/src/core/product_members/UserProductMemberEmbeddedList.tsx new file mode 100644 index 000000000..1aae10cd4 --- /dev/null +++ b/frontend/src/core/product_members/UserProductMemberEmbeddedList.tsx @@ -0,0 +1,63 @@ +import { + Datagrid, + Identifier, + ListContextProvider, + RaRecord, + SelectField, + TextField, + useListController, +} from "react-admin"; + +import { ROLE_CHOICES } from "../../access_control/types"; +import { CustomPagination } from "../../commons/custom_fields/CustomPagination"; +import { getSettingListSize } from "../../commons/user_settings/functions"; + +type UserProductMemberEmbeddedListProps = { + user: any; + is_product_group: boolean; +}; + +function productLabel(is_product_group: boolean): string { + return is_product_group ? "Product Group" : "Product"; +} + +const showProduct = (id: Identifier, resource: string, record: RaRecord) => { + if (record.product_data.is_product_group) { + return "../../../../product_groups/" + record.product_data.id + "/show/members"; + } + return "../../../../products/" + record.product_data.id + "/show/members"; +}; + +const UserProductMemberEmbeddedList = ({ user, is_product_group }: UserProductMemberEmbeddedListProps) => { + const listContext = useListController({ + filter: { user: Number(user.id), is_product_group: is_product_group }, + perPage: 25, + resource: "product_members", + sort: { field: "product_data.name", order: "ASC" }, + disableSyncWithLocation: true, + }); + + if (listContext.isLoading) { + return
Loading...
; + } + + return ( + +
+ + + + + +
+
+ ); +}; + +export default UserProductMemberEmbeddedList; diff --git a/frontend/src/core/products/ProductHeader.tsx b/frontend/src/core/products/ProductHeader.tsx index ace02dec4..37ea327aa 100644 --- a/frontend/src/core/products/ProductHeader.tsx +++ b/frontend/src/core/products/ProductHeader.tsx @@ -1,4 +1,4 @@ -import { Paper } from "@mui/material"; +import { Box, Paper, Typography } from "@mui/material"; import { Labeled, RecordContextProvider, TextField, useGetOne } from "react-admin"; import { useParams } from "react-router-dom"; @@ -23,24 +23,32 @@ const ProductHeader = () => { - - - - {product && product.security_gate_passed != undefined && ( + + Product + + + + + + {product && product.security_gate_passed != undefined && ( + + + + )} - + - )} - - - + ); diff --git a/mkdocs_requirements.txt b/mkdocs_requirements.txt index 166be22a7..fc3c6cb9f 100644 --- a/mkdocs_requirements.txt +++ b/mkdocs_requirements.txt @@ -1 +1 @@ -mkdocs-material==9.5.39 # https://github.com/squidfunk/mkdocs-material +mkdocs-material==9.5.40 # https://github.com/squidfunk/mkdocs-material diff --git a/so_configuration_sca_current.yml b/so_configuration_sca_current.yml index d840af2fa..024b0c0b3 100644 --- a/so_configuration_sca_current.yml +++ b/so_configuration_sca_current.yml @@ -1,18 +1,18 @@ trivy_image_backend_current: SCANNER: trivy_image - TARGET: "maibornwolff/secobserve-backend:1.19.0" + TARGET: "maibornwolff/secobserve-backend:1.20.0" FURTHER_PARAMETERS: "--pkg-types os" REPORT_NAME: "trivy_backend_image.json" SO_ORIGIN_SERVICE: "backend" - SO_BRANCH_NAME: "1.19.0" + SO_BRANCH_NAME: "1.20.0" trivy_image_frontend_current: SCANNER: trivy_image - TARGET: "maibornwolff/secobserve-frontend:1.19.0" + TARGET: "maibornwolff/secobserve-frontend:1.20.0" FURTHER_PARAMETERS: "--pkg-types os" REPORT_NAME: "trivy_frontend_image.json" SO_ORIGIN_SERVICE: "frontend" - SO_BRANCH_NAME: "1.19.0" + SO_BRANCH_NAME: "1.20.0" trivy_filesystem_backend_current: SCANNER: trivy_filesystem @@ -20,7 +20,7 @@ trivy_filesystem_backend_current: TARGET: "backend/poetry.lock" REPORT_NAME: "trivy_backend_poetry.json" SO_ORIGIN_SERVICE: "backend" - SO_BRANCH_NAME: "1.19.0" + SO_BRANCH_NAME: "1.20.0" trivy_filesystem_frontend_current: SCANNER: trivy_filesystem @@ -28,7 +28,7 @@ trivy_filesystem_frontend_current: TARGET: "frontend/package-lock.json" REPORT_NAME: "trivy_frontend_npm.json" SO_ORIGIN_SERVICE: "frontend" - SO_BRANCH_NAME: "1.19.0" + SO_BRANCH_NAME: "1.20.0" importer: SO_UPLOAD: "true"