Skip to content

Commit 0d2f294

Browse files
committed
Added the monitoring and review, last review date and review frequency functionality.
1 parent 7a9554f commit 0d2f294

10 files changed

Lines changed: 196 additions & 3 deletions
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types=1);
2+
/**
3+
* @link https://github.com/monarc-project for the canonical source repository
4+
* @copyright Copyright (c) 2016-2026 Luxembourg House of Cybersecurity LHC.lu - Licensed under GNU Affero GPL v3
5+
* @license MONARC is licensed under GNU Affero General Public License version 3
6+
*/
7+
8+
use Phinx\Migration\AbstractMigration;
9+
10+
class AddRiskReviewMetadata extends AbstractMigration
11+
{
12+
public function up(): void
13+
{
14+
$this->table('instances_risks')
15+
->addColumn('last_review_date', 'date', ['null' => true, 'after' => 'comment_after'])
16+
->addColumn('review_frequency', 'string', ['limit' => 50, 'null' => true, 'after' => 'last_review_date'])
17+
->update();
18+
19+
$this->table('anr_reassessment_triggers')
20+
->addColumn('monitoring_approach', 'text', ['null' => true, 'after' => 'description'])
21+
->update();
22+
}
23+
24+
public function down(): void
25+
{
26+
$this->table('instances_risks')
27+
->removeColumn('review_frequency')
28+
->removeColumn('last_review_date')
29+
->update();
30+
31+
$this->table('anr_reassessment_triggers')
32+
->removeColumn('monitoring_approach')
33+
->update();
34+
}
35+
}

src/Controller/ApiAnrInstancesRisksController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public function update($id, $data)
6060
'id' => $instanceRisk->getId(),
6161
'riskSourceId' => $instanceRisk->getRiskSource()?->getId(),
6262
'riskSourceLabel' => $instanceRisk->getRiskSource()?->getLabel() ?? '',
63+
'lastReviewDate' => $instanceRisk->getLastReviewDate()?->format('Y-m-d'),
64+
'reviewFrequency' => $instanceRisk->getReviewFrequency(),
6365
'threatRate' => $instanceRisk->getThreatRate(),
6466
'vulnerabilityRate' => $instanceRisk->getVulnerabilityRate(),
6567
'reductionAmount' => $instanceRisk->getReductionAmount(),

src/Controller/ApiAnrReassessmentTriggersController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ private function prepareReassessmentTriggerData(ReassessmentTrigger $reassessmen
102102
'id' => $reassessmentTrigger->getId(),
103103
'triggerType' => $reassessmentTrigger->getTriggerType(),
104104
'description' => $reassessmentTrigger->getDescription(),
105+
'monitoringApproach' => $reassessmentTrigger->getMonitoringApproach(),
105106
'isActive' => $reassessmentTrigger->isActive(),
106107
'position' => $reassessmentTrigger->getPosition(),
107108
];

src/Export/Service/AnrExportService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ private function prepareReassessmentTriggersData(Entity\Anr $anr): array
574574
$result[] = [
575575
'triggerType' => $reassessmentTrigger->getTriggerType(),
576576
'description' => $reassessmentTrigger->getDescription(),
577+
'monitoringApproach' => $reassessmentTrigger->getMonitoringApproach(),
577578
'isActive' => $reassessmentTrigger->isActive(),
578579
'position' => $reassessmentTrigger->getPosition(),
579580
];

src/Export/Service/Traits/InformationInstanceRiskExportTrait.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ private function prepareInformationInstanceRiskData(
7070
'riskAvailability' => $withEval ? $instanceRisk->getRiskAvailability() : -1,
7171
'context' => $withEval ? $instanceRisk->getContext() : '',
7272
'riskOwner' => $withEval ? $instanceRisk->getInstanceRiskOwner()?->getName() : '',
73+
'lastReviewDate' => $withEval ? $instanceRisk->getLastReviewDate()?->format('Y-m-d') : null,
74+
'reviewFrequency' => $withEval ? $instanceRisk->getReviewFrequency() : null,
7375
'recommendations' => $recommendationsData,
7476
];
7577
}

src/Import/Processor/InstanceRiskImportProcessor.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Monarc\FrontOffice\Import\Processor;
99

10+
use DateTime;
1011
use Monarc\Core\Entity\ScaleSuperClass;
1112
use Monarc\FrontOffice\Entity;
1213
use Monarc\FrontOffice\Import\Helper\ImportCacheHelper;
@@ -114,6 +115,17 @@ private function processInstanceRiskData(Entity\Instance $instance, array $insta
114115
->setIsThreatRateNotSetOrModifiedExternally(
115116
(bool)$instanceRiskData['isThreatRateNotSetOrModifiedExternally']
116117
);
118+
if (array_key_exists('lastReviewDate', $instanceRiskData)) {
119+
$instanceRisk->setLastReviewDate(
120+
empty($instanceRiskData['lastReviewDate'])
121+
? null
122+
: (DateTime::createFromFormat('Y-m-d', $instanceRiskData['lastReviewDate']) ?: null)
123+
);
124+
}
125+
if (array_key_exists('reviewFrequency', $instanceRiskData)) {
126+
$reviewFrequency = trim((string)$instanceRiskData['reviewFrequency']);
127+
$instanceRisk->setReviewFrequency($reviewFrequency === '' ? null : $reviewFrequency);
128+
}
117129
if (!empty($instanceRiskData['riskOwner'])) {
118130
$this->instanceRiskOwnerService
119131
->processRiskOwnerNameAndAssign($instanceRiskData['riskOwner'], $instanceRisk);
@@ -196,6 +208,8 @@ private function applyRiskDataToItsSibling(
196208
$toInstanceRisk
197209
->setContext($fromInstanceRisk->getContext())
198210
->setInstanceRiskOwner($fromInstanceRisk->getInstanceRiskOwner())
211+
->setLastReviewDate($fromInstanceRisk->getLastReviewDate())
212+
->setReviewFrequency($fromInstanceRisk->getReviewFrequency())
199213
->setThreatRate($fromInstanceRisk->getThreatRate())
200214
->setVulnerabilityRate($fromInstanceRisk->getVulnerabilityRate())
201215
->setKindOfMeasure($fromInstanceRisk->getKindOfMeasure())

src/Import/Traits/ImportDataStructureAdapterTrait.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ private function prepareInstanceRisksData(array $data, int $languageIndex): arra
284284
'riskAvailability' => $instanceRiskDatum['riskD'],
285285
'context' => $instanceRiskDatum['context'],
286286
'riskOwner' => $instanceRiskDatum['riskOwner'],
287+
'lastReviewDate' => $instanceRiskDatum['lastReviewDate'] ?? null,
288+
'reviewFrequency' => $instanceRiskDatum['reviewFrequency'] ?? null,
287289
'recommendations' => $recommendationsData,
288290
];
289291
}

src/Service/AnrInstanceRiskService.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Monarc\FrontOffice\Service;
99

10+
use DateTime;
1011
use Dom\Entity as DomEntity;
1112
use Monarc\Core\Exception\Exception;
1213
use Monarc\Core\Entity as CoreEntity;
@@ -127,6 +128,12 @@ public function getInstanceRisks(Entity\Anr $anr, ?int $instanceId, array $param
127128
'comment' => $instanceRisk->getComment(),
128129
'scope' => $object->getScope(),
129130
'kindOfMeasure' => $instanceRisk->getKindOfMeasure(),
131+
'lastReviewDate' => $instanceRisk->getLastReviewDate()?->format('Y-m-d'),
132+
'reviewFrequency' => $instanceRisk->getReviewFrequency(),
133+
'reviewFrequencyLabel' => $this->getReviewFrequencyLabel(
134+
$instanceRisk->getReviewFrequency(),
135+
$languageIndex
136+
),
130137
't' => $instanceRisk->isTreated(),
131138
'tid' => $threat->getUuid(),
132139
'vid' => $vulnerability->getUuid(),
@@ -232,6 +239,15 @@ public function createInstanceRisks(
232239
);
233240
$instanceRisk->setInstanceRiskOwner($instanceRiskOwner);
234241
}
242+
if (array_key_exists('lastReviewDate', $instanceRiskData)) {
243+
$instanceRisk->setLastReviewDate(
244+
$this->createLastReviewDateFromString($instanceRiskData['lastReviewDate'])
245+
);
246+
}
247+
if (array_key_exists('reviewFrequency', $instanceRiskData)) {
248+
$reviewFrequency = trim((string)$instanceRiskData['reviewFrequency']);
249+
$instanceRisk->setReviewFrequency($reviewFrequency === '' ? null : $reviewFrequency);
250+
}
235251
}
236252
}
237253

@@ -380,6 +396,8 @@ public function getInstanceRisksInCsv(Entity\Anr $anr, int $instanceId = null, a
380396
$this->translateService->translate('Residual risk', $languageIndex),
381397
$this->translateService->translate('Risk owner', $languageIndex),
382398
$this->translateService->translate('Risk context', $languageIndex),
399+
$this->translateService->translate('Last review date', $languageIndex),
400+
$this->translateService->translate('Review frequency', $languageIndex),
383401
$this->translateService->translate('Recommendations', $languageIndex),
384402
$this->translateService->translate('Security referentials', $languageIndex),
385403
]) . "\n";
@@ -450,6 +468,8 @@ public function getInstanceRisksInCsv(Entity\Anr $anr, int $instanceId = null, a
450468
$instanceRisk->getCacheTargetedRisk() === -1 ? null : $instanceRisk->getCacheTargetedRisk(),
451469
$instanceRisk->getInstanceRiskOwner() ? $instanceRisk->getInstanceRiskOwner()->getName() : '',
452470
$instanceRisk->getContext(),
471+
$instanceRisk->getLastReviewDate()?->format('Y-m-d'),
472+
$this->getReviewFrequencyLabel($instanceRisk->getReviewFrequency(), $languageIndex),
453473
implode("\n", $recommendationData),
454474
implode("\n", $measuresData),
455475
];
@@ -471,6 +491,13 @@ private function updateInstanceRiskData(Entity\InstanceRisk $instanceRisk, array
471491
$riskSourceId = empty($data['riskSourceId']) ? null : $data['riskSourceId'];
472492
$instanceRisk->setRiskSource($this->riskSourceTable->findById((int)$riskSourceId));
473493
}
494+
if (array_key_exists('lastReviewDate', $data)) {
495+
$instanceRisk->setLastReviewDate($this->prepareLastReviewDate($instanceRisk, $data['lastReviewDate']));
496+
}
497+
if (array_key_exists('reviewFrequency', $data)) {
498+
$reviewFrequency = trim((string)$data['reviewFrequency']);
499+
$instanceRisk->setReviewFrequency($reviewFrequency === '' ? null : $reviewFrequency);
500+
}
474501
if (isset($data['owner'])) {
475502
$this->instanceRiskOwnerService->processRiskOwnerNameAndAssign((string)$data['owner'], $instanceRisk);
476503
}
@@ -518,6 +545,57 @@ private function getOrCreateRiskSourceByLabel(Entity\Anr $anr, string $label): E
518545
return $riskSource;
519546
}
520547

548+
private function prepareLastReviewDate(Entity\InstanceRisk $instanceRisk, mixed $lastReviewDate): ?DateTime
549+
{
550+
$normalizedDate = $this->createLastReviewDateFromString($lastReviewDate);
551+
$currentLastReviewDate = $instanceRisk->getLastReviewDate();
552+
if (
553+
$normalizedDate !== null
554+
&& $currentLastReviewDate !== null
555+
&& $normalizedDate->format('Y-m-d') !== $currentLastReviewDate->format('Y-m-d')
556+
&& $normalizedDate <= $currentLastReviewDate
557+
) {
558+
throw new Exception('Last review date must be later than the existing last review date.', 412);
559+
}
560+
561+
return $normalizedDate;
562+
}
563+
564+
private function createLastReviewDateFromString(mixed $lastReviewDate): ?DateTime
565+
{
566+
if ($lastReviewDate === null || $lastReviewDate === '') {
567+
return null;
568+
}
569+
570+
$normalizedDate = DateTime::createFromFormat('Y-m-d', (string)$lastReviewDate);
571+
if ($normalizedDate === false) {
572+
throw new Exception('Invalid last review date format.', 412);
573+
}
574+
575+
return $normalizedDate;
576+
}
577+
578+
private function getReviewFrequencyLabel(?string $reviewFrequency, int $languageIndex): string
579+
{
580+
if ($reviewFrequency === null || $reviewFrequency === '') {
581+
return '';
582+
}
583+
584+
$suggestedReviewFrequencies = [
585+
'Monthly',
586+
'Quarterly',
587+
'Semi-annually',
588+
'Annually',
589+
'On trigger',
590+
];
591+
592+
if (\in_array($reviewFrequency, $suggestedReviewFrequencies, true)) {
593+
return $this->translateService->translate($reviewFrequency, $languageIndex);
594+
}
595+
596+
return $reviewFrequency;
597+
}
598+
521599
private function duplicateRecommendationRisks(
522600
Entity\InstanceRisk $fromInstanceRisk,
523601
Entity\InstanceRisk $newInstanceRisk

0 commit comments

Comments
 (0)