AuditLogProjector::applyAuditableEvent()   C
last analyzed

Complexity

Conditions 12
Paths 257

Size

Total Lines 63
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 35
nc 257
nop 2
dl 0
loc 63
rs 5.4208
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupMiddleware\ApiBundle\Identity\Projector;
20
21
use Broadway\Domain\DomainMessage;
22
use DateTime as CoreDateTime;
23
use Ramsey\Uuid\Uuid;
24
use Surfnet\Stepup\DateTime\DateTime;
25
use Surfnet\Stepup\Identity\AuditLog\Metadata;
26
use Surfnet\Stepup\Identity\Event\AuditableEvent;
27
use Surfnet\Stepup\Identity\Event\CompliedWithRecoveryCodeRevocationEvent;
28
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
29
use Surfnet\Stepup\Identity\Event\RecoveryTokenRevokedEvent;
30
use Surfnet\Stepup\Identity\Value\CommonName;
31
use Surfnet\Stepup\Identity\Value\Institution;
32
use Surfnet\Stepup\Identity\Value\RecoveryTokenIdentifierFactory;
33
use Surfnet\Stepup\Identity\Value\RecoveryTokenType;
34
use Surfnet\Stepup\Identity\Value\SecondFactorId;
35
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
36
use Surfnet\Stepup\Identity\Value\VettingType;
37
use Surfnet\Stepup\Projector\Projector;
38
use Surfnet\StepupBundle\Value\SecondFactorType;
39
use Surfnet\StepupMiddleware\ApiBundle\Exception\RuntimeException;
40
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\AuditLogEntry;
41
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity;
42
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\AuditLogRepository;
43
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
44
45
/**
46
 * @SuppressWarnings("PHPMD.CouplingBetweenObjects")
47
 */
48
class AuditLogProjector extends Projector
49
{
50
    public function __construct(
51
        private readonly AuditLogRepository $auditLogRepository,
52
        private readonly IdentityRepository $identityRepository,
53
    ) {
54
    }
55
56
    /**
57
     * @param DomainMessage $domainMessage
58
     */
59
    public function handle(DomainMessage $domainMessage): void
60
    {
61
        $event = $domainMessage->getPayload();
62
63
        switch (true) {
64
            case $event instanceof IdentityForgottenEvent:
65
                // Don't insert the IdentityForgottenEvent into the audit log, as we'd remove it immediately afterwards.
66
                $this->applyIdentityForgottenEvent($event);
67
                break;
68
            // Finally apply the auditable event, most events are auditable this so first handle the unique variants
69
            case $event instanceof AuditableEvent:
70
                $this->applyAuditableEvent($event, $domainMessage);
71
                break;
72
        }
73
    }
74
75
    /**
76
     * @SuppressWarnings("PHPMD.CyclomaticComplexity")
77
     * @SuppressWarnings("PHPMD.NPathComplexity")
78
     */
79
    private function applyAuditableEvent(AuditableEvent $event, DomainMessage $domainMessage): void
80
    {
81
        $auditLogMetadata = $event->getAuditLogMetadata();
82
83
        $metadata = $domainMessage->getMetadata()->serialize();
84
        $entry = new AuditLogEntry();
85
        $entry->id = (string)Uuid::uuid4();
86
87
        if (isset($metadata['actorId'])) {
88
            $actor = $this->identityRepository->find($metadata['actorId']);
89
90
            if (!$actor instanceof Identity) {
91
                throw new RuntimeException(
92
                    sprintf(
93
                        'Cannot create AuditLogEntry, given Actor Identity "%s" does not exist',
94
                        $metadata['actorId'],
95
                    ),
96
                );
97
            }
98
99
            $entry->actorId = $metadata['actorId'];
100
            $entry->actorCommonName = $actor->commonName;
101
        }
102
103
        $this->augmentActorCommonName($entry, $auditLogMetadata);
104
105
        if (isset($metadata['actorInstitution'])) {
106
            $entry->actorInstitution = new Institution($metadata['actorInstitution']);
107
        }
108
109
        $entry->identityId = (string)$auditLogMetadata->identityId;
110
        $entry->identityInstitution = $auditLogMetadata->identityInstitution;
111
        $entry->event = $event::class;
112
        $entry->recordedOn = new DateTime(new CoreDateTime($domainMessage->getRecordedOn()->toString()));
113
114
        if ($auditLogMetadata->secondFactorId instanceof SecondFactorId) {
115
            $entry->secondFactorId = (string)$auditLogMetadata->secondFactorId;
116
        }
117
118
        if ($auditLogMetadata->secondFactorType instanceof SecondFactorType) {
119
            $entry->secondFactorType = (string)$auditLogMetadata->secondFactorType;
120
        }
121
122
        if (!$event instanceof RecoveryTokenRevokedEvent
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
123
            && !$event instanceof CompliedWithRecoveryCodeRevocationEvent
124
            && $auditLogMetadata->recoveryTokenId
125
        ) {
126
            $entry->recoveryTokenIdentifier = (string)$auditLogMetadata->recoveryTokenId;
127
        }
128
129
        if ($auditLogMetadata->recoveryTokenType instanceof RecoveryTokenType) {
130
            $entry->recoveryTokenType = (string)$auditLogMetadata->recoveryTokenType;
131
        }
132
133
        if ($auditLogMetadata->secondFactorIdentifier instanceof SecondFactorIdentifier) {
134
            $entry->secondFactorIdentifier = (string)$auditLogMetadata->secondFactorIdentifier;
135
        }
136
137
        if ($auditLogMetadata->raInstitution instanceof Institution) {
138
            $entry->raInstitution = (string)$auditLogMetadata->raInstitution;
139
        }
140
141
        $this->auditLogRepository->save($entry);
142
    }
143
144
    protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void
145
    {
146
        $entries = $this->auditLogRepository->findByIdentityId($event->identityId);
147
        foreach ($entries as $auditLogEntry) {
148
            $auditLogEntry->actorCommonName = CommonName::unknown();
149
150
            if ($auditLogEntry->recoveryTokenIdentifier) {
151
                $auditLogEntry->recoveryTokenIdentifier = RecoveryTokenIdentifierFactory::unknownForType(
152
                    new RecoveryTokenType($auditLogEntry->recoveryTokenType),
153
                );
154
            }
155
        }
156
157
        $entriesWhereActor = $this->auditLogRepository->findEntriesWhereIdentityIsActorOnly($event->identityId);
158
        foreach ($entriesWhereActor as $auditLogEntry) {
159
            $auditLogEntry->actorCommonName = CommonName::unknown();
160
        }
161
162
        $this->auditLogRepository->saveAll($entries);
163
        $this->auditLogRepository->saveAll($entriesWhereActor);
164
    }
165
166
    private function augmentActorCommonName(AuditLogEntry $entry, Metadata $auditLogMetadata): void
167
    {
168
        if ($auditLogMetadata->vettingType instanceof VettingType) {
169
            $entry->actorCommonName = new CommonName(
170
                $entry->actorCommonName->getCommonName() . $auditLogMetadata->vettingType->auditLog()
0 ignored issues
show
Bug introduced by
The method getCommonName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

170
                $entry->actorCommonName->/** @scrutinizer ignore-call */ 
171
                                         getCommonName() . $auditLogMetadata->vettingType->auditLog()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
171
            );
172
        }
173
    }
174
}
175