CommandController::resolveInstitution()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 2
dl 0
loc 17
rs 10
c 0
b 0
f 0
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
 */
0 ignored issues
show
Coding Style introduced by
Missing @link tag in file comment
Loading history...
18
19
namespace Surfnet\StepupMiddleware\ApiBundle\Controller;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\Stepup\Identity\Value\IdentityId;
23
use Surfnet\Stepup\Identity\Value\Institution;
24
use Surfnet\StepupMiddleware\ApiBundle\Authorization\Service\CommandAuthorizationService;
25
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\AbstractCommand;
26
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\Command;
27
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\Metadata;
28
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\SelfServiceExecutable;
29
use Surfnet\StepupMiddleware\CommandHandlingBundle\EventSourcing\MetadataEnricher;
30
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\ForbiddenException;
31
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
32
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
33
use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\TransactionAwarePipeline;
34
use Surfnet\StepupMiddleware\ApiBundle\Controller\AbstractController;
35
use Symfony\Component\HttpFoundation\JsonResponse;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
38
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
39
use function sprintf;
40
41
/**
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
44
class CommandController extends AbstractController
45
{
46
    public function __construct(
47
        private readonly TransactionAwarePipeline $pipeline,
48
        private readonly MetadataEnricher $metadataEnricher,
49
        private readonly AuthorizationCheckerInterface $authorizationChecker,
50
        private readonly LoggerInterface $logger,
51
        private readonly CommandAuthorizationService $commandAuthorizationService,
52
    ) {
53
    }
54
55
    public function handle(AbstractCommand $command, Metadata $metadata, Request $request): JsonResponse
56
    {
57
        $this->denyAccessUnlessGrantedOneOff(['ROLE_RA', 'ROLE_SS']);
58
        $this->logger->notice(sprintf('Received request to process Command "%s"', $command));
59
60
        $this->metadataEnricher->setMetadata($metadata);
61
62
        if ($this->authorizationChecker->isGranted('ROLE_MANAGEMENT')) {
63
            $this->logger->notice('Command sent through Management API, not enforcing Whitelist');
64
        } else {
65
            $this->logger->notice('Ensuring that the actor institution is on the whitelist, or the actor is SRAA');
66
67
            $this->handleAuthorization($command, $metadata);
68
        }
69
70
        try {
71
            $command = $this->pipeline->process($command);
72
        } catch (ForbiddenException $e) {
73
            throw new AccessDeniedHttpException(
74
                sprintf('Processing of command "%s" is forbidden for this client', $command),
75
                $e,
76
            );
77
        }
78
79
        $serverName = $request->server->get('SERVER_NAME') ?: $request->server->get('SERVER_ADDR');
80
        $response = new JsonResponse(['command' => $command->UUID, 'processed_by' => $serverName]);
81
82
        $this->logger->notice(sprintf('Command "%s" has been successfully processed', $command));
83
84
        return $response;
85
    }
86
87
    private function resolveInstitution(Command $command, Metadata $metadata): Institution
0 ignored issues
show
Coding Style introduced by
Private method name "CommandController::resolveInstitution" must be prefixed with an underscore
Loading history...
88
    {
89
        if ($metadata->actorInstitution) {
90
            return new Institution($metadata->actorInstitution);
91
        }
92
93
        // the createIdentityCommand is used to create an Identity for a new user,
94
        // the updateIdentityCommand is used to update name or email of an identity
95
        // Both are only sent by the SS when the Identity is not logged in yet,
96
        // thus there is not Metadata::actorInstitution,
97
        if ($command instanceof CreateIdentityCommand || $command instanceof UpdateIdentityCommand) {
98
            return new Institution($command->institution);
0 ignored issues
show
Bug introduced by
The property institution does not seem to exist on Surfnet\StepupMiddleware...d\UpdateIdentityCommand.
Loading history...
99
        }
100
101
        // conservative, if we cannot determine an institution, deny processing.
102
        throw new AccessDeniedHttpException(
103
            'Cannot reliably determine the institution of the actor, denying processing of command',
104
        );
105
    }
106
107
    private function handleAuthorization(Command $command, Metadata $metadata): void
0 ignored issues
show
Coding Style introduced by
Private method name "CommandController::handleAuthorization" must be prefixed with an underscore
Loading history...
108
    {
109
        // Get the actorId and actorInstitution from the metadata
110
        // Be aware that these values could be null when executing commands where we shouldn't log in for
111
        // - CreateIdentityCommand
112
        // - UpdateIdentityCommand
113
        $actorId = is_null($metadata->actorId) ? null : new IdentityId($metadata->actorId);
114
        $actorInstitution = is_null($metadata->actorInstitution) ? null : new Institution($metadata->actorInstitution);
115
116
        // The institution of an actor should be whitelisted or the actor should be SRAA
117
        // Be aware that the actor metadata is not always present, see self::resolveInstitution
118
        $this->logger->notice('Ensuring that the actor institution is on the whitelist, or the actor is SRAA');
119
        $institution = $this->resolveInstitution($command, $metadata);
120
        if (!$this->commandAuthorizationService->isInstitutionWhitelisted($institution, $actorId)) {
121
            throw new AccessDeniedHttpException(
122
                sprintf(
123
                    'Institution "%s" is not on the whitelist and actor "%s" is not an SRAA, processing of command denied',
124
                    $institution,
125
                    $metadata->actorId,
126
                ),
127
            );
128
        }
129
130
        $this->logger->notice(
131
            'Ensuring that the actor is allowed to execute a command based on the fine grained authorization configuration',
132
        );
133
134
        // Validate that if a command is an SelfServiceExecutable we may execute the command
135
        // This should be an SRAA or the actor itself
136
        // Be aware that for the CreateIdentityCommand and UpdateIdentityCommand the actorId is unknown because we aren't logged in yet
137
        if (!$this->commandAuthorizationService->maySelfserviceCommandBeExecutedOnBehalfOf(
138
            $command,
139
            $actorId,
140
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
141
            $message = 'Processing of SelfService command denied, see log entries for details';
142
            if ($command instanceof SelfServiceExecutable) {
0 ignored issues
show
introduced by
$command is always a sub-type of Surfnet\StepupMiddleware...d\SelfServiceExecutable.
Loading history...
143
                $message = sprintf(
144
                    'The actor "%s" is not allowed to act on behalf of identity "%s" processing of SelfService command denied',
145
                    new IdentityId($metadata->actorId),
0 ignored issues
show
Bug introduced by
It seems like $metadata->actorId can also be of type null; however, parameter $value of Surfnet\Stepup\Identity\...entityId::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

145
                    new IdentityId(/** @scrutinizer ignore-type */ $metadata->actorId),
Loading history...
146
                    $command->getIdentityId(),
147
                );
148
            }
149
            throw new AccessDeniedHttpException($message);
150
        }
151
152
        // Validate that if a command is an RAExecutable we may execute the command
153
        // This should be an SRAA or an RAA which is configured to act on behalf of the institution
154
        if (!$this->commandAuthorizationService->mayRaCommandBeExecutedOnBehalfOf(
155
            $command,
156
            $actorId,
157
            $actorInstitution,
158
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
159
            throw new AccessDeniedHttpException(
160
                sprintf(
161
                    'The actor "%s" is not allowed to act on behalf of institution  "%s" processing of RA command denied',
162
                    new IdentityId($metadata->actorId),
163
                    $institution,
164
                ),
165
            );
166
        }
167
    }
168
}
169