wbcompliance 2.2.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbcompliance/__init__.py +1 -0
- wbcompliance/admin/__init__.py +16 -0
- wbcompliance/admin/compliance_form.py +56 -0
- wbcompliance/admin/compliance_task.py +135 -0
- wbcompliance/admin/compliance_type.py +8 -0
- wbcompliance/admin/risk_management/__init__.py +3 -0
- wbcompliance/admin/risk_management/checks.py +7 -0
- wbcompliance/admin/risk_management/incidents.py +50 -0
- wbcompliance/admin/risk_management/rules.py +63 -0
- wbcompliance/admin/utils.py +46 -0
- wbcompliance/apps.py +14 -0
- wbcompliance/factories/__init__.py +21 -0
- wbcompliance/factories/compliance.py +246 -0
- wbcompliance/factories/risk_management/__init__.py +12 -0
- wbcompliance/factories/risk_management/backends.py +42 -0
- wbcompliance/factories/risk_management/checks.py +12 -0
- wbcompliance/factories/risk_management/incidents.py +84 -0
- wbcompliance/factories/risk_management/rules.py +100 -0
- wbcompliance/filters/__init__.py +2 -0
- wbcompliance/filters/compliances.py +189 -0
- wbcompliance/filters/risk_management/__init__.py +3 -0
- wbcompliance/filters/risk_management/checks.py +22 -0
- wbcompliance/filters/risk_management/incidents.py +113 -0
- wbcompliance/filters/risk_management/rules.py +110 -0
- wbcompliance/filters/risk_management/tables.py +112 -0
- wbcompliance/filters/risk_management/utils.py +3 -0
- wbcompliance/management/__init__.py +10 -0
- wbcompliance/migrations/0001_initial_squashed_squashed_0010_alter_checkedobjectincidentrelationship_resolved_by_and_more.py +1744 -0
- wbcompliance/migrations/0011_alter_riskrule_parameters.py +21 -0
- wbcompliance/migrations/0012_alter_compliancetype_options.py +20 -0
- wbcompliance/migrations/0013_alter_riskrule_unique_together.py +16 -0
- wbcompliance/migrations/0014_alter_reviewcompliancetask_year.py +27 -0
- wbcompliance/migrations/0015_auto_20240103_0957.py +43 -0
- wbcompliance/migrations/0016_checkedobjectincidentrelationship_report_details_and_more.py +37 -0
- wbcompliance/migrations/0017_alter_rulebackend_incident_report_template.py +20 -0
- wbcompliance/migrations/0018_alter_rulecheckedobjectrelationship_unique_together.py +39 -0
- wbcompliance/migrations/0019_rulegroup_riskrule_activation_date_and_more.py +60 -0
- wbcompliance/migrations/__init__.py +0 -0
- wbcompliance/models/__init__.py +20 -0
- wbcompliance/models/compliance_form.py +626 -0
- wbcompliance/models/compliance_task.py +800 -0
- wbcompliance/models/compliance_type.py +133 -0
- wbcompliance/models/enums.py +13 -0
- wbcompliance/models/risk_management/__init__.py +4 -0
- wbcompliance/models/risk_management/backend.py +139 -0
- wbcompliance/models/risk_management/checks.py +194 -0
- wbcompliance/models/risk_management/dispatch.py +41 -0
- wbcompliance/models/risk_management/incidents.py +619 -0
- wbcompliance/models/risk_management/mixins.py +115 -0
- wbcompliance/models/risk_management/rules.py +654 -0
- wbcompliance/permissions.py +32 -0
- wbcompliance/serializers/__init__.py +30 -0
- wbcompliance/serializers/compliance_form.py +320 -0
- wbcompliance/serializers/compliance_task.py +463 -0
- wbcompliance/serializers/compliance_type.py +26 -0
- wbcompliance/serializers/risk_management/__init__.py +19 -0
- wbcompliance/serializers/risk_management/checks.py +53 -0
- wbcompliance/serializers/risk_management/incidents.py +227 -0
- wbcompliance/serializers/risk_management/rules.py +158 -0
- wbcompliance/tasks.py +112 -0
- wbcompliance/tests/__init__.py +0 -0
- wbcompliance/tests/conftest.py +63 -0
- wbcompliance/tests/disable_signals.py +82 -0
- wbcompliance/tests/mixins.py +17 -0
- wbcompliance/tests/risk_management/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/__init__.py +0 -0
- wbcompliance/tests/risk_management/models/test_backends.py +0 -0
- wbcompliance/tests/risk_management/models/test_checks.py +55 -0
- wbcompliance/tests/risk_management/models/test_incidents.py +327 -0
- wbcompliance/tests/risk_management/models/test_rules.py +255 -0
- wbcompliance/tests/signals.py +89 -0
- wbcompliance/tests/test_filters.py +23 -0
- wbcompliance/tests/test_models.py +57 -0
- wbcompliance/tests/test_serializers.py +48 -0
- wbcompliance/tests/test_views.py +377 -0
- wbcompliance/tests/tests.py +21 -0
- wbcompliance/urls.py +238 -0
- wbcompliance/viewsets/__init__.py +40 -0
- wbcompliance/viewsets/buttons/__init__.py +9 -0
- wbcompliance/viewsets/buttons/compliance_form.py +78 -0
- wbcompliance/viewsets/buttons/compliance_task.py +149 -0
- wbcompliance/viewsets/buttons/risk_managment/__init__.py +3 -0
- wbcompliance/viewsets/buttons/risk_managment/checks.py +11 -0
- wbcompliance/viewsets/buttons/risk_managment/incidents.py +51 -0
- wbcompliance/viewsets/buttons/risk_managment/rules.py +35 -0
- wbcompliance/viewsets/compliance_form.py +425 -0
- wbcompliance/viewsets/compliance_task.py +513 -0
- wbcompliance/viewsets/compliance_type.py +38 -0
- wbcompliance/viewsets/display/__init__.py +22 -0
- wbcompliance/viewsets/display/compliance_form.py +317 -0
- wbcompliance/viewsets/display/compliance_task.py +453 -0
- wbcompliance/viewsets/display/compliance_type.py +22 -0
- wbcompliance/viewsets/display/risk_managment/__init__.py +11 -0
- wbcompliance/viewsets/display/risk_managment/checks.py +46 -0
- wbcompliance/viewsets/display/risk_managment/incidents.py +155 -0
- wbcompliance/viewsets/display/risk_managment/rules.py +146 -0
- wbcompliance/viewsets/display/risk_managment/tables.py +51 -0
- wbcompliance/viewsets/endpoints/__init__.py +27 -0
- wbcompliance/viewsets/endpoints/compliance_form.py +207 -0
- wbcompliance/viewsets/endpoints/compliance_task.py +193 -0
- wbcompliance/viewsets/endpoints/compliance_type.py +9 -0
- wbcompliance/viewsets/endpoints/risk_managment/__init__.py +12 -0
- wbcompliance/viewsets/endpoints/risk_managment/checks.py +16 -0
- wbcompliance/viewsets/endpoints/risk_managment/incidents.py +36 -0
- wbcompliance/viewsets/endpoints/risk_managment/rules.py +32 -0
- wbcompliance/viewsets/endpoints/risk_managment/tables.py +14 -0
- wbcompliance/viewsets/menu/__init__.py +17 -0
- wbcompliance/viewsets/menu/compliance_form.py +49 -0
- wbcompliance/viewsets/menu/compliance_task.py +130 -0
- wbcompliance/viewsets/menu/compliance_type.py +17 -0
- wbcompliance/viewsets/menu/risk_management.py +56 -0
- wbcompliance/viewsets/risk_management/__init__.py +21 -0
- wbcompliance/viewsets/risk_management/checks.py +49 -0
- wbcompliance/viewsets/risk_management/incidents.py +204 -0
- wbcompliance/viewsets/risk_management/mixins.py +52 -0
- wbcompliance/viewsets/risk_management/rules.py +179 -0
- wbcompliance/viewsets/risk_management/tables.py +96 -0
- wbcompliance/viewsets/titles/__init__.py +17 -0
- wbcompliance/viewsets/titles/compliance_form.py +101 -0
- wbcompliance/viewsets/titles/compliance_task.py +60 -0
- wbcompliance/viewsets/titles/compliance_type.py +13 -0
- wbcompliance/viewsets/titles/risk_managment/__init__.py +1 -0
- wbcompliance/viewsets/titles/risk_managment/checks.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/incidents.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/rules.py +0 -0
- wbcompliance/viewsets/titles/risk_managment/tables.py +7 -0
- wbcompliance-2.2.1.dist-info/METADATA +7 -0
- wbcompliance-2.2.1.dist-info/RECORD +129 -0
- wbcompliance-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from django.contrib.auth.models import Permission
|
|
3
|
+
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from django.contrib.messages.api import warning
|
|
5
|
+
from django.contrib.postgres.fields.ranges import DateRangeField
|
|
6
|
+
from django.db.models import (
|
|
7
|
+
Case,
|
|
8
|
+
CharField,
|
|
9
|
+
DateField,
|
|
10
|
+
Exists,
|
|
11
|
+
ExpressionWrapper,
|
|
12
|
+
F,
|
|
13
|
+
IntegerField,
|
|
14
|
+
OuterRef,
|
|
15
|
+
Subquery,
|
|
16
|
+
Value,
|
|
17
|
+
When,
|
|
18
|
+
)
|
|
19
|
+
from django.db.models.functions import Cast, Concat
|
|
20
|
+
from django.http import HttpResponse
|
|
21
|
+
from django.utils.functional import cached_property
|
|
22
|
+
from rest_framework import status
|
|
23
|
+
from rest_framework.decorators import action
|
|
24
|
+
from wbcompliance.filters import (
|
|
25
|
+
CheckedObjectIncidentRelationshipFilterSet,
|
|
26
|
+
RiskIncidentFilterSet,
|
|
27
|
+
)
|
|
28
|
+
from wbcompliance.models import RuleThreshold
|
|
29
|
+
from wbcompliance.models.risk_management import (
|
|
30
|
+
CheckedObjectIncidentRelationship,
|
|
31
|
+
RiskIncident,
|
|
32
|
+
RiskIncidentType,
|
|
33
|
+
RiskRule,
|
|
34
|
+
)
|
|
35
|
+
from wbcompliance.models.risk_management.incidents import resolve_all_incidents_as_task
|
|
36
|
+
from wbcompliance.permissions import RulePermission
|
|
37
|
+
from wbcompliance.serializers import (
|
|
38
|
+
CheckedObjectIncidentRelationshipModelSerializer,
|
|
39
|
+
CheckedObjectIncidentRelationshipRepresentationSerializer,
|
|
40
|
+
RiskIncidentModelSerializer,
|
|
41
|
+
RiskIncidentRepresentationSerializer,
|
|
42
|
+
RiskIncidentTypeRepresentationSerializer,
|
|
43
|
+
)
|
|
44
|
+
from wbcompliance.viewsets.buttons import RiskIncidentButtonConfig
|
|
45
|
+
from wbcompliance.viewsets.display import (
|
|
46
|
+
CheckedObjectIncidentRelationshipRiskRuleDisplayConfig,
|
|
47
|
+
RiskIncidentDisplayConfig,
|
|
48
|
+
)
|
|
49
|
+
from wbcompliance.viewsets.endpoints import (
|
|
50
|
+
CheckedObjectIncidentRelationshipEndpointConfig,
|
|
51
|
+
CheckedObjectIncidentRelationshipRiskRuleEndpointConfig,
|
|
52
|
+
RiskIncidentEndpointConfig,
|
|
53
|
+
)
|
|
54
|
+
from wbcore import viewsets
|
|
55
|
+
from wbcore.contrib.guardian.models import UserObjectPermission
|
|
56
|
+
|
|
57
|
+
User = get_user_model()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RiskIncidentTypeRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
61
|
+
queryset = RiskIncidentType.objects.all()
|
|
62
|
+
serializer_class = RiskIncidentTypeRepresentationSerializer
|
|
63
|
+
search_fields = ("name",)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RiskIncidentRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
67
|
+
queryset = RiskIncident.objects.all()
|
|
68
|
+
serializer_class = RiskIncidentRepresentationSerializer
|
|
69
|
+
search_fields = (
|
|
70
|
+
"rule__name",
|
|
71
|
+
"breached_object_repr",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CheckedObjectIncidentRelationshipRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
76
|
+
queryset = CheckedObjectIncidentRelationship.objects.all()
|
|
77
|
+
serializer_class = CheckedObjectIncidentRelationshipRepresentationSerializer
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RiskIncidentModelViewSet(viewsets.ModelViewSet):
|
|
81
|
+
permission_classes = [RulePermission]
|
|
82
|
+
display_config_class = RiskIncidentDisplayConfig
|
|
83
|
+
endpoint_config_class = RiskIncidentEndpointConfig
|
|
84
|
+
button_config_class = RiskIncidentButtonConfig
|
|
85
|
+
|
|
86
|
+
ordering_fields = [
|
|
87
|
+
"status",
|
|
88
|
+
"date_range",
|
|
89
|
+
"rule__name",
|
|
90
|
+
"object_repr",
|
|
91
|
+
"severity__name",
|
|
92
|
+
"resolved_by__computed_str",
|
|
93
|
+
"comment",
|
|
94
|
+
]
|
|
95
|
+
ordering = ["-date_range", "-severity__severity_order", "id"]
|
|
96
|
+
serializer_class = RiskIncidentModelSerializer
|
|
97
|
+
queryset = RiskIncident.objects.select_related(
|
|
98
|
+
"resolved_by",
|
|
99
|
+
"breached_content_type",
|
|
100
|
+
"rule",
|
|
101
|
+
"severity",
|
|
102
|
+
).prefetch_related("checked_object_relationships")
|
|
103
|
+
filterset_class = RiskIncidentFilterSet
|
|
104
|
+
|
|
105
|
+
@action(detail=False, methods=["PATCH"])
|
|
106
|
+
def resolveallincidents(self, request, pk=None):
|
|
107
|
+
resolve_status = request.POST.get("resolve_status", RiskIncident.Status.RESOLVED.name)
|
|
108
|
+
resolve_all_incidents_as_task.delay(
|
|
109
|
+
request.user.id,
|
|
110
|
+
request.POST.get("comment", ""),
|
|
111
|
+
resolve_status == RiskIncident.Status.RESOLVED.name,
|
|
112
|
+
rule_id=request.GET.get("rule_id", None),
|
|
113
|
+
)
|
|
114
|
+
return HttpResponse("All opened incidents are resolved", status=status.HTTP_200_OK)
|
|
115
|
+
|
|
116
|
+
@cached_property
|
|
117
|
+
def rule_content_type(self):
|
|
118
|
+
return ContentType.objects.get_for_model(RiskRule)
|
|
119
|
+
|
|
120
|
+
@cached_property
|
|
121
|
+
def view_rule_permission(self):
|
|
122
|
+
return Permission.objects.get(codename="view_riskrule", content_type__app_label="wbcompliance")
|
|
123
|
+
|
|
124
|
+
def get_queryset(self):
|
|
125
|
+
"""
|
|
126
|
+
We protect the queryset to allow only user to see their respective incidents
|
|
127
|
+
"""
|
|
128
|
+
queryset = super().get_queryset()
|
|
129
|
+
if not self.request.user.has_perm("administrate_riskrule"):
|
|
130
|
+
queryset = queryset.annotate(
|
|
131
|
+
rule_pk=Cast(
|
|
132
|
+
"rule_id", output_field=CharField()
|
|
133
|
+
), # We need to cast the rule id into a charfield otherwise postgres complains with the operator
|
|
134
|
+
have_view_permission_on_rule=Exists(
|
|
135
|
+
UserObjectPermission.objects.filter(
|
|
136
|
+
permission=self.view_rule_permission,
|
|
137
|
+
content_type=self.rule_content_type,
|
|
138
|
+
object_pk=OuterRef("rule_pk"),
|
|
139
|
+
user=self.request.user,
|
|
140
|
+
)
|
|
141
|
+
),
|
|
142
|
+
).filter(have_view_permission_on_rule=True)
|
|
143
|
+
queryset = queryset.annotate(
|
|
144
|
+
has_subincidents=Exists(CheckedObjectIncidentRelationship.objects.filter(incident=OuterRef("pk"))),
|
|
145
|
+
_group_key=Case(When(has_subincidents=True, then=F("id")), default=None, output_field=IntegerField()),
|
|
146
|
+
checked_date=ExpressionWrapper(F("date_range__endswith") - 1, output_field=DateField()),
|
|
147
|
+
object_repr=Concat(Value("Breached: "), F("breached_object_repr")),
|
|
148
|
+
threshold_repr=Subquery(
|
|
149
|
+
RuleThreshold.objects.filter(severity=OuterRef("severity"), rule=OuterRef("rule")).values(
|
|
150
|
+
"computed_str"
|
|
151
|
+
)[:1]
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
return queryset
|
|
155
|
+
|
|
156
|
+
def add_messages(self, request, instance: RiskIncident | None = None, **kwargs):
|
|
157
|
+
if instance:
|
|
158
|
+
if instance.status == RiskIncident.Status.OPEN and instance.last_ignored_date:
|
|
159
|
+
warning(
|
|
160
|
+
request,
|
|
161
|
+
f"This incident was automatically reopened after being ignored from {instance.last_ignored_date:%Y-%m-%d} to {instance.get_ignore_until_date():%Y-%m-%d}",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class RiskIncidentRiskRuleModelViewSet(RiskIncidentModelViewSet):
|
|
166
|
+
permission_classes = [RulePermission]
|
|
167
|
+
|
|
168
|
+
def get_queryset(self):
|
|
169
|
+
return super().get_queryset().filter(rule=self.kwargs["rule_id"])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class CheckedObjectIncidentRelationshipModelViewSet(viewsets.ModelViewSet):
|
|
173
|
+
permission_classes = [RulePermission]
|
|
174
|
+
ordering = ["-rule_check__evaluation_date"]
|
|
175
|
+
ordering_fields = ["status", "object_repr", "checked_date"]
|
|
176
|
+
serializer_class = CheckedObjectIncidentRelationshipModelSerializer
|
|
177
|
+
queryset = CheckedObjectIncidentRelationship.objects.select_related(
|
|
178
|
+
"resolved_by",
|
|
179
|
+
"incident",
|
|
180
|
+
"rule_check",
|
|
181
|
+
"severity",
|
|
182
|
+
).annotate(
|
|
183
|
+
object_repr=F("rule_check__rule_checked_object_relationship__checked_object_repr"),
|
|
184
|
+
checked_date=F("rule_check__evaluation_date"),
|
|
185
|
+
date_range=Cast(
|
|
186
|
+
Concat(Value("["), F("checked_date"), Value(","), F("checked_date"), Value("]")),
|
|
187
|
+
output_field=DateRangeField(),
|
|
188
|
+
),
|
|
189
|
+
threshold_repr=Subquery(
|
|
190
|
+
RuleThreshold.objects.filter(severity=OuterRef("severity"), rule=OuterRef("incident__rule")).values(
|
|
191
|
+
"computed_str"
|
|
192
|
+
)[:1]
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
display_config_class = CheckedObjectIncidentRelationshipRiskRuleDisplayConfig
|
|
196
|
+
endpoint_config_class = CheckedObjectIncidentRelationshipEndpointConfig
|
|
197
|
+
filterset_class = CheckedObjectIncidentRelationshipFilterSet
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CheckedObjectIncidentRelationshipRiskRuleModelViewSet(CheckedObjectIncidentRelationshipModelViewSet):
|
|
201
|
+
endpoint_config_class = CheckedObjectIncidentRelationshipRiskRuleEndpointConfig
|
|
202
|
+
|
|
203
|
+
def get_queryset(self):
|
|
204
|
+
return super().get_queryset().filter(incident=self.kwargs["incident_id"])
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from rest_framework.reverse import reverse
|
|
5
|
+
from wbcompliance.models.risk_management.checks import RiskCheck
|
|
6
|
+
from wbcore.contrib.icons import WBIcon
|
|
7
|
+
from wbcore.metadata.configs import buttons as bt
|
|
8
|
+
from wbcore.signals import add_additional_resource, add_instance_button
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RiskCheckViewSetMixin:
|
|
12
|
+
@classmethod
|
|
13
|
+
def _get_risk_checks_button_title(cls) -> str:
|
|
14
|
+
return "Checks"
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def _add_risk_check_button(cls, sender, many, *args, view=None, **kwargs):
|
|
18
|
+
icon = WBIcon.CONFIRM.icon
|
|
19
|
+
with suppress(AssertionError):
|
|
20
|
+
if view and (obj := view.get_object()):
|
|
21
|
+
icon = RiskCheck.CheckStatus[obj.get_worst_check_status()].icon
|
|
22
|
+
return bt.WidgetButton(key="checks", label=cls._get_risk_checks_button_title(), icon=icon)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def _add_risk_check_add_additional_resource(
|
|
26
|
+
cls, sender, serializer, instance, request, user, view=None, is_list=False, **kwargs
|
|
27
|
+
):
|
|
28
|
+
if not is_list and instance.checks.exists() and (content_type := ContentType.objects.get_for_model(instance)):
|
|
29
|
+
return {
|
|
30
|
+
"checks": f'{reverse("wbcompliance:riskcheck-list", args=[], request=request)}?triggers=[[{content_type.id},{instance.id}]]'
|
|
31
|
+
}
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def options(self, request, *args, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
Handler method for HTTP 'OPTIONS' request.
|
|
37
|
+
"""
|
|
38
|
+
add_instance_button.connect(
|
|
39
|
+
self._add_risk_check_button,
|
|
40
|
+
sender=self.__class__,
|
|
41
|
+
dispatch_uid="wbcompliance_add_instance_button_riskcheck",
|
|
42
|
+
)
|
|
43
|
+
return super().options(request, *args, **kwargs)
|
|
44
|
+
|
|
45
|
+
def get_serializer_class(self):
|
|
46
|
+
serializer_class = super().get_serializer_class()
|
|
47
|
+
add_additional_resource.connect(
|
|
48
|
+
self._add_risk_check_add_additional_resource,
|
|
49
|
+
sender=serializer_class,
|
|
50
|
+
dispatch_uid="wbcompliance_add_additional_resource_riskcheck",
|
|
51
|
+
)
|
|
52
|
+
return serializer_class
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from django.contrib.auth import get_user_model
|
|
3
|
+
from django.db.models import Case, CharField, Count, OuterRef, Q, Subquery, Value, When
|
|
4
|
+
from django.db.models.functions import Coalesce
|
|
5
|
+
from django.http import HttpResponse
|
|
6
|
+
from django.shortcuts import get_object_or_404
|
|
7
|
+
from rest_framework import status
|
|
8
|
+
from rest_framework.decorators import action
|
|
9
|
+
from wbcompliance.filters import RiskRuleFilterSet
|
|
10
|
+
from wbcompliance.models.risk_management import (
|
|
11
|
+
RiskIncident,
|
|
12
|
+
RiskRule,
|
|
13
|
+
RuleBackend,
|
|
14
|
+
RuleCheckedObjectRelationship,
|
|
15
|
+
RuleThreshold,
|
|
16
|
+
)
|
|
17
|
+
from wbcompliance.models.risk_management.rules import RuleGroup, process_rule_as_task
|
|
18
|
+
from wbcompliance.permissions import RulePermission
|
|
19
|
+
from wbcompliance.serializers import (
|
|
20
|
+
RiskRuleModelSerializer,
|
|
21
|
+
RiskRuleRepresentationSerializer,
|
|
22
|
+
RuleBackendRepresentationSerializer,
|
|
23
|
+
RuleCheckedObjectRelationshipModelSerializer,
|
|
24
|
+
RuleCheckedObjectRelationshipRepresentationSerializer,
|
|
25
|
+
RuleGroupRepresentationSerializer,
|
|
26
|
+
RuleThresholdModelSerializer,
|
|
27
|
+
RuleThresholdRepresentationSerializer,
|
|
28
|
+
)
|
|
29
|
+
from wbcompliance.viewsets.buttons import RiskRuleButtonConfig
|
|
30
|
+
from wbcompliance.viewsets.display import (
|
|
31
|
+
RiskRuleDisplayConfig,
|
|
32
|
+
RuleCheckedObjectRelationshipRiskRuleDisplayConfig,
|
|
33
|
+
RuleThresholdRiskRuleDisplayConfig,
|
|
34
|
+
)
|
|
35
|
+
from wbcompliance.viewsets.endpoints import (
|
|
36
|
+
RiskRuleEndpointConfig,
|
|
37
|
+
RuleCheckedObjectRelationshipRiskRuleEndpointConfig,
|
|
38
|
+
RuleThresholdRiskRuleEndpointConfig,
|
|
39
|
+
)
|
|
40
|
+
from wbcore import serializers as wb_serializers
|
|
41
|
+
from wbcore import viewsets
|
|
42
|
+
from wbcore.contrib.guardian.viewsets.mixins import GuardianFilterMixin
|
|
43
|
+
from wbcore.utils.date import get_date_interval_from_request
|
|
44
|
+
|
|
45
|
+
User = get_user_model()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class RuleGroupRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
49
|
+
queryset = RuleGroup.objects.all()
|
|
50
|
+
serializer_class = RuleGroupRepresentationSerializer
|
|
51
|
+
search_fields = ("name",)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RuleCheckedObjectRelationshipRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
55
|
+
queryset = RuleCheckedObjectRelationship.objects.all()
|
|
56
|
+
serializer_class = RuleCheckedObjectRelationshipRepresentationSerializer
|
|
57
|
+
search_fields = ("checked_object_repr",)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RuleBackendRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
61
|
+
queryset = RuleBackend.objects.all()
|
|
62
|
+
serializer_class = RuleBackendRepresentationSerializer
|
|
63
|
+
search_fields = ("name",)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RuleThresholdRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
67
|
+
queryset = RuleThreshold.objects.all()
|
|
68
|
+
serializer_class = RuleThresholdRepresentationSerializer
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RiskRuleRepresentationViewSet(GuardianFilterMixin, viewsets.RepresentationViewSet):
|
|
72
|
+
queryset = RiskRule.objects.all()
|
|
73
|
+
serializer_class = RiskRuleRepresentationSerializer
|
|
74
|
+
search_fields = (
|
|
75
|
+
"name",
|
|
76
|
+
"description",
|
|
77
|
+
"rule_backend__name",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RuleThresholdRiskRuleModelViewSet(viewsets.ModelViewSet):
|
|
82
|
+
permission_classes = [RulePermission]
|
|
83
|
+
display_config_class = RuleThresholdRiskRuleDisplayConfig
|
|
84
|
+
endpoint_config_class = RuleThresholdRiskRuleEndpointConfig
|
|
85
|
+
queryset = RuleThreshold.objects.all()
|
|
86
|
+
serializer_class = RuleThresholdModelSerializer
|
|
87
|
+
filterset_fields = {
|
|
88
|
+
"severity": ["exact"],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def get_queryset(self):
|
|
92
|
+
return super().get_queryset().filter(rule_id=self.kwargs["rule_id"])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class RuleCheckedObjectRelationshipRiskRuleModelViewSet(viewsets.ModelViewSet):
|
|
96
|
+
permission_classes = [RulePermission]
|
|
97
|
+
display_config_class = RuleCheckedObjectRelationshipRiskRuleDisplayConfig
|
|
98
|
+
endpoint_config_class = RuleCheckedObjectRelationshipRiskRuleEndpointConfig
|
|
99
|
+
queryset = RuleCheckedObjectRelationship.objects.all()
|
|
100
|
+
serializer_class = RuleCheckedObjectRelationshipModelSerializer
|
|
101
|
+
search_fields = ["checked_object_repr"]
|
|
102
|
+
filterset_fields = {
|
|
103
|
+
"checked_object_repr": ["icontains"],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def get_queryset(self):
|
|
107
|
+
return super().get_queryset().filter(rule_id=self.kwargs["rule_id"])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class RiskRuleModelViewSet(GuardianFilterMixin, viewsets.ModelViewSet):
|
|
111
|
+
display_config_class = RiskRuleDisplayConfig
|
|
112
|
+
endpoint_config_class = RiskRuleEndpointConfig
|
|
113
|
+
button_config_class = RiskRuleButtonConfig
|
|
114
|
+
|
|
115
|
+
queryset = RiskRule.objects.all()
|
|
116
|
+
serializer_class = RiskRuleModelSerializer
|
|
117
|
+
filterset_class = RiskRuleFilterSet
|
|
118
|
+
|
|
119
|
+
search_fields = ("name", "description", "checked_object_relationships__checked_object_repr")
|
|
120
|
+
ordering_fields = (
|
|
121
|
+
"name",
|
|
122
|
+
"is_enable",
|
|
123
|
+
"only_passive_check_allowed",
|
|
124
|
+
"is_silent",
|
|
125
|
+
"is_mandatory",
|
|
126
|
+
"open_incidents_count",
|
|
127
|
+
)
|
|
128
|
+
ordering = ("name",)
|
|
129
|
+
|
|
130
|
+
def get_serializer_class(self):
|
|
131
|
+
if "pk" in self.kwargs:
|
|
132
|
+
if rule := self.get_object():
|
|
133
|
+
parameter_fields = rule.rule_backend.backend_class.get_serializer_class().get_parameter_fields()
|
|
134
|
+
|
|
135
|
+
class TmpSerializer(RiskRuleModelSerializer):
|
|
136
|
+
class Meta(RiskRuleModelSerializer.Meta):
|
|
137
|
+
flatten_fields = {
|
|
138
|
+
"parameters": wb_serializers.JSONTableField(
|
|
139
|
+
serializer_class=rule.rule_backend.backend_class.get_serializer_class(),
|
|
140
|
+
flatten_field_names=parameter_fields,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return TmpSerializer
|
|
145
|
+
return RiskRuleModelSerializer
|
|
146
|
+
|
|
147
|
+
def _subquery_open_risk_incidents_count(self):
|
|
148
|
+
queryset = RiskIncident.objects.filter(status=RiskIncident.Status.OPEN, rule=OuterRef("pk"))
|
|
149
|
+
return Coalesce(Subquery(queryset.values("rule").annotate(c=Count("rule")).values("c")[:1]), 0)
|
|
150
|
+
|
|
151
|
+
def get_queryset(self):
|
|
152
|
+
return (
|
|
153
|
+
super()
|
|
154
|
+
.get_queryset()
|
|
155
|
+
.annotate(
|
|
156
|
+
open_incidents_count=self._subquery_open_risk_incidents_count(),
|
|
157
|
+
in_breach=Case(
|
|
158
|
+
When(Q(open_incidents_count__gt=0) & Q(is_enable=True), then=Value("BREACH")),
|
|
159
|
+
When(Q(open_incidents_count=0) & Q(is_enable=True), then=Value("PASSED")),
|
|
160
|
+
default=Value("INACTIVE"),
|
|
161
|
+
output_field=CharField(),
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
.select_related(
|
|
165
|
+
"rule_backend",
|
|
166
|
+
"creator",
|
|
167
|
+
)
|
|
168
|
+
.prefetch_related("incidents")
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@action(detail=True, methods=["PATCH"])
|
|
172
|
+
def recheck(self, request, pk=None):
|
|
173
|
+
start, end = get_date_interval_from_request(request, request_type="POST")
|
|
174
|
+
if start and end:
|
|
175
|
+
rule = get_object_or_404(RiskRule, pk=pk)
|
|
176
|
+
for evaluation_date in pd.date_range(start, end, freq="B"):
|
|
177
|
+
process_rule_as_task.delay(rule.id, evaluation_date, override_incident=False)
|
|
178
|
+
return HttpResponse("Rule is checking", status=status.HTTP_200_OK)
|
|
179
|
+
return HttpResponse("Wrong arguments", status=status.HTTP_400_BAD_REQUEST)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from django.db.models import Q
|
|
5
|
+
from django.utils.functional import cached_property
|
|
6
|
+
from django.utils.translation import gettext as _
|
|
7
|
+
from wbcompliance.filters import RiskManagementIncidentFilter
|
|
8
|
+
from wbcompliance.models.risk_management import (
|
|
9
|
+
CheckedObjectIncidentRelationship,
|
|
10
|
+
RiskRule,
|
|
11
|
+
)
|
|
12
|
+
from wbcore.pandas import fields as pf
|
|
13
|
+
from wbcore.pandas.views import PandasAPIViewSet
|
|
14
|
+
from wbcore.utils.models import get_object
|
|
15
|
+
|
|
16
|
+
from ..display import RiskManagementIncidentTableDisplayConfig
|
|
17
|
+
from ..endpoints import RiskManagementIncidentTableEndpointConfig
|
|
18
|
+
from ..titles import RiskManagementIncidentTableTitleConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RiskManagementIncidentTableView(PandasAPIViewSet):
|
|
22
|
+
IDENTIFIER = "wbcompliance:riskmanagementincidentpandas"
|
|
23
|
+
filterset_class = RiskManagementIncidentFilter
|
|
24
|
+
queryset = CheckedObjectIncidentRelationship.objects.all()
|
|
25
|
+
|
|
26
|
+
display_config_class = RiskManagementIncidentTableDisplayConfig
|
|
27
|
+
title_config_class = RiskManagementIncidentTableTitleConfig
|
|
28
|
+
endpoint_config_class = RiskManagementIncidentTableEndpointConfig
|
|
29
|
+
|
|
30
|
+
@cached_property
|
|
31
|
+
def checked_object_content_type(self):
|
|
32
|
+
if checked_object_content_type_id := self.request.GET.get("checked_object_content_type", None):
|
|
33
|
+
return ContentType.objects.get(id=checked_object_content_type_id)
|
|
34
|
+
|
|
35
|
+
@cached_property
|
|
36
|
+
def get_rule_map(self):
|
|
37
|
+
rules = RiskRule.objects.filter(
|
|
38
|
+
Q(is_enable=True)
|
|
39
|
+
& (
|
|
40
|
+
Q(rule_backend__allowed_checked_object_content_type__isnull=True)
|
|
41
|
+
| Q(rule_backend__allowed_checked_object_content_type=self.checked_object_content_type)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
return list(map(lambda x: (f'rule_{x["id"]}', x["name"]), rules.order_by("name").values("id", "name")))
|
|
45
|
+
|
|
46
|
+
def get_ordering_fields(self):
|
|
47
|
+
return ["checked_object_repr", *[x[0] for x in self.get_rule_map]]
|
|
48
|
+
|
|
49
|
+
def get_pandas_fields(self, request):
|
|
50
|
+
fields = [
|
|
51
|
+
pf.PKField(key="id", label=_("ID")),
|
|
52
|
+
pf.CharField(key="checked_object_repr", label=_("ID")),
|
|
53
|
+
]
|
|
54
|
+
for key, label in self.get_rule_map:
|
|
55
|
+
fields.append(pf.IntegerField(key=key, label=label))
|
|
56
|
+
return pf.PandasFields(fields=tuple(fields))
|
|
57
|
+
|
|
58
|
+
def get_queryset(self):
|
|
59
|
+
return CheckedObjectIncidentRelationship.objects.filter(incident__rule__is_enable=True)
|
|
60
|
+
|
|
61
|
+
def get_dataframe(self, request, queryset, **kwargs):
|
|
62
|
+
df = pd.DataFrame()
|
|
63
|
+
if checked_object_content_type := self.checked_object_content_type:
|
|
64
|
+
df = pd.DataFrame(
|
|
65
|
+
queryset.values(
|
|
66
|
+
"rule_check__rule_checked_object_relationship__checked_object_id",
|
|
67
|
+
"incident__rule__id",
|
|
68
|
+
"severity__severity_order",
|
|
69
|
+
),
|
|
70
|
+
columns=[
|
|
71
|
+
"rule_check__rule_checked_object_relationship__checked_object_id",
|
|
72
|
+
"incident__rule__id",
|
|
73
|
+
"severity__severity_order",
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
df = df.rename(
|
|
77
|
+
columns={
|
|
78
|
+
"rule_check__rule_checked_object_relationship__checked_object_id": "id",
|
|
79
|
+
"incident__rule__id": "rule",
|
|
80
|
+
"severity__severity_order": "severity",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if not df.empty:
|
|
85
|
+
df = (
|
|
86
|
+
df.pivot_table(index="id", columns=["rule"], values="severity", aggfunc=np.max, fill_value=-1)
|
|
87
|
+
.rename_axis(None, axis=1)
|
|
88
|
+
.astype("int")
|
|
89
|
+
)
|
|
90
|
+
df = df.rename(columns=lambda x: f"rule_{x}")
|
|
91
|
+
df = df.reset_index()
|
|
92
|
+
df["checked_object_repr"] = df.id.apply(
|
|
93
|
+
lambda x: str(get_object(checked_object_content_type.model_class(), x))
|
|
94
|
+
)
|
|
95
|
+
df = df.where(pd.notnull(df), -1)
|
|
96
|
+
return df
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .compliance_form import (
|
|
2
|
+
ComplianceFormSectionRuleTitleConfig,
|
|
3
|
+
ComplianceFormSectionTitleConfig,
|
|
4
|
+
ComplianceFormSignatureSectionRuleTitleConfig,
|
|
5
|
+
ComplianceFormSignatureTitleConfig,
|
|
6
|
+
ComplianceFormTitleConfig,
|
|
7
|
+
)
|
|
8
|
+
from .compliance_task import (
|
|
9
|
+
ComplianceTaskComplianceTaskGroupTitleConfig,
|
|
10
|
+
ComplianceTaskGroupTitleConfig,
|
|
11
|
+
ComplianceTaskInstanceTitleConfig,
|
|
12
|
+
ComplianceTaskMatrixPandasTitleConfig,
|
|
13
|
+
ComplianceTaskTitleConfig,
|
|
14
|
+
ReviewComplianceTaskTitleConfig,
|
|
15
|
+
)
|
|
16
|
+
from .compliance_type import ComplianceTypeTitleConfig
|
|
17
|
+
from .risk_managment import *
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from django.utils.translation import gettext as _
|
|
2
|
+
from wbcompliance.models import (
|
|
3
|
+
ComplianceForm,
|
|
4
|
+
ComplianceFormRule,
|
|
5
|
+
ComplianceFormSection,
|
|
6
|
+
ComplianceFormSignatureRule,
|
|
7
|
+
ComplianceFormSignatureSection,
|
|
8
|
+
)
|
|
9
|
+
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ComplianceFormTitleConfig(TitleViewConfig):
|
|
13
|
+
def get_instance_title(self):
|
|
14
|
+
return _("Compliance Form: {{title}} - version {{version}}")
|
|
15
|
+
|
|
16
|
+
def get_list_title(self):
|
|
17
|
+
return _("Compliance Forms")
|
|
18
|
+
|
|
19
|
+
def get_create_title(self):
|
|
20
|
+
return _("New Compliance Form")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ComplianceFormSignatureTitleConfig(TitleViewConfig):
|
|
24
|
+
def get_instance_title(self):
|
|
25
|
+
return _("Compliance Form Signature: {{_compliance_form.title}} - version {{_compliance_form.version}}")
|
|
26
|
+
|
|
27
|
+
def get_list_title(self):
|
|
28
|
+
return _("Compliance Form Signatures")
|
|
29
|
+
|
|
30
|
+
def get_create_title(self):
|
|
31
|
+
return _("New Compliance Form Signature")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ComplianceFormSectionTitleConfig(TitleViewConfig):
|
|
35
|
+
def get_instance_title(self):
|
|
36
|
+
if ref_id := self.view.kwargs.get("pk"):
|
|
37
|
+
_section = ComplianceFormSection.objects.get(id=ref_id)
|
|
38
|
+
return _("Compliance Form Section: {} of Compliance Form: - version {}").format(
|
|
39
|
+
_section.name, _section.compliance_form.title, _section.compliance_form.version
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return _("Compliance Form Section: {{name}}")
|
|
43
|
+
|
|
44
|
+
def get_list_title(self):
|
|
45
|
+
if compliance_form_id := self.view.kwargs.get("compliance_form_id"):
|
|
46
|
+
compliance_form = ComplianceForm.objects.get(id=compliance_form_id)
|
|
47
|
+
return _("Compliance Form: {} - version {}").format(compliance_form.title, compliance_form.version)
|
|
48
|
+
return _("Compliance Form Sections")
|
|
49
|
+
|
|
50
|
+
def get_create_title(self):
|
|
51
|
+
if compliance_form_id := self.view.kwargs.get("compliance_form_id"):
|
|
52
|
+
compliance_form = ComplianceForm.objects.get(id=compliance_form_id)
|
|
53
|
+
return _("New Section of Compliance Form: {} - version {}").format(
|
|
54
|
+
compliance_form.title, compliance_form.version
|
|
55
|
+
)
|
|
56
|
+
return _("New Section")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ComplianceFormSectionRuleTitleConfig(TitleViewConfig):
|
|
60
|
+
def get_instance_title(self):
|
|
61
|
+
if ref_id := self.view.kwargs.get("pk"):
|
|
62
|
+
_rule = ComplianceFormRule.objects.get(id=ref_id)
|
|
63
|
+
return _("Compliance Form Rule: {} of Section: {}").format(_rule.id, _rule.section.name)
|
|
64
|
+
return _("Compliance Form Rule: {{id}}")
|
|
65
|
+
|
|
66
|
+
def get_list_title(self):
|
|
67
|
+
if section_id := self.view.kwargs.get("section_id"):
|
|
68
|
+
section = ComplianceFormSection.objects.get(id=section_id)
|
|
69
|
+
return _("Compliance Form: {} - version {}").format(
|
|
70
|
+
section.compliance_form.title, section.compliance_form.version
|
|
71
|
+
)
|
|
72
|
+
return _("Compliance Form Rules")
|
|
73
|
+
|
|
74
|
+
def get_create_title(self):
|
|
75
|
+
if section_id := self.view.kwargs.get("section_id"):
|
|
76
|
+
section = ComplianceFormSection.objects.get(id=section_id)
|
|
77
|
+
return _("New Rule of Section: {}").format(section.name)
|
|
78
|
+
return _("New Rule")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ComplianceFormSignatureSectionRuleTitleConfig(TitleViewConfig):
|
|
82
|
+
def get_instance_title(self):
|
|
83
|
+
if ref_id := self.view.kwargs.get("pk"):
|
|
84
|
+
_rule = ComplianceFormSignatureRule.objects.get(id=ref_id)
|
|
85
|
+
return _("Compliance Form Signature Rule: {} of Section: {}").format(_rule.id, _rule.section.name)
|
|
86
|
+
return _("Compliance Form Signature Rule: {{id}}")
|
|
87
|
+
|
|
88
|
+
def get_list_title(self):
|
|
89
|
+
if section_id := self.view.kwargs.get("section_id"):
|
|
90
|
+
section = ComplianceFormSignatureSection.objects.get(id=section_id)
|
|
91
|
+
return _("Compliance Form Signature: {} - version {}").format(
|
|
92
|
+
section.compliance_form_signature.compliance_form.title,
|
|
93
|
+
section.compliance_form_signature.compliance_form.version,
|
|
94
|
+
)
|
|
95
|
+
return _("Compliance Form Signature Rules")
|
|
96
|
+
|
|
97
|
+
def get_create_title(self):
|
|
98
|
+
if section_id := self.view.kwargs.get("section_id"):
|
|
99
|
+
section = ComplianceFormSignatureSection.objects.get(id=section_id)
|
|
100
|
+
return _("New Rule of Section: {}").format(section.name)
|
|
101
|
+
return _("New Rule")
|