Completed
Pull Request — develop (#290)
by
unknown
05:53 queued 02:45
created

CommandController   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
dl 0
loc 188
c 0
b 0
f 0
wmc 22
lcom 1
cbo 18
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 1
A handleAction() 0 35 4
A resolveInstitution() 0 19 4
A assertCommandMayBeProcessedOnBehalfOf() 0 23 4
A isSraa() 0 13 3
B assertRaaCommandMayBeExecuted() 0 30 6
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\Controller;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\Stepup\Configuration\Value\InstitutionRole;
23
use Surfnet\Stepup\Identity\Value\IdentityId;
24
use Surfnet\Stepup\Identity\Value\Institution;
25
use Surfnet\StepupMiddleware\ApiBundle\Authorization\Service\InstitutionAuthorizationService;
26
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\IdentityService;
27
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\WhitelistService;
28
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\Command;
29
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\Metadata;
30
use Surfnet\StepupMiddleware\CommandHandlingBundle\Command\RaExecutable;
31
use Surfnet\StepupMiddleware\CommandHandlingBundle\EventSourcing\MetadataEnricher;
32
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\ForbiddenException;
33
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
34
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
35
use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\TransactionAwarePipeline;
36
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
37
use Symfony\Component\HttpFoundation\JsonResponse;
38
use Symfony\Component\HttpFoundation\Request;
39
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
40
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
41
42
/**
43
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
44
 */
45
class CommandController extends Controller
46
{
47
    /**
48
     * @var WhitelistService
49
     */
50
    private $whitelistService;
51
52
    /**
53
     * @var IdentityService
54
     */
55
    private $identityService;
56
57
    /**
58
     * @var TransactionAwarePipeline
59
     */
60
    private $pipeline;
61
62
    /**
63
     * @var MetadataEnricher
64
     */
65
    private $metadataEnricher;
66
67
    /**
68
     * @var AuthorizationChecker
69
     */
70
    private $authorizationChecker;
71
72
    /**
73
     * @var LoggerInterface
74
     */
75
    private $logger;
76
    /**
77
     * @var InstitutionAuthorizationService
78
     */
79
    private $institutionAuthorizationService;
80
81
    public function __construct(
82
        TransactionAwarePipeline $pipeline,
83
        WhitelistService $whitelistService,
84
        IdentityService $identityService,
85
        MetadataEnricher $enricher,
86
        AuthorizationChecker $authorizationChecker,
87
        LoggerInterface $logger,
88
        InstitutionAuthorizationService $institutionAuthorizationService
89
    ) {
90
        $this->pipeline = $pipeline;
91
        $this->whitelistService = $whitelistService;
92
        $this->identityService = $identityService;
93
        $this->authorizationChecker = $authorizationChecker;
94
        $this->metadataEnricher = $enricher;
95
        $this->logger = $logger;
96
        $this->institutionAuthorizationService = $institutionAuthorizationService;
97
    }
98
99
    public function handleAction(Command $command, Metadata $metadata, Request $request)
100
    {
101
        $this->denyAccessUnlessGranted(['ROLE_RA', 'ROLE_SS']);
102
103
        $logger = $this->logger;
104
        $logger->notice(sprintf('Received request to process Command "%s"', $command));
105
106
        $this->metadataEnricher->setMetadata($metadata);
107
108
        if ($this->authorizationChecker->isGranted('ROLE_MANAGEMENT')) {
109
            $logger->notice('Command sent through Management API, not enforcing Whitelist');
110
        } else {
111
            $logger->notice('Ensuring that the actor institution is on the whitelist, or the actor is SRAA');
112
113
            $institution = $this->resolveInstitution($command, $metadata);
114
            $this->assertCommandMayBeProcessedOnBehalfOf($institution, $metadata->actorId);
115
            $this->assertRaaCommandMayBeExecuted($metadata->actorId, $metadata->actorInstitution, $command);
116
        }
117
118
        try {
119
            $command = $this->pipeline->process($command);
120
        } catch (ForbiddenException $e) {
121
            throw new AccessDeniedHttpException(
122
                sprintf('Processing of command "%s" is forbidden for this client', $command),
123
                $e
124
            );
125
        }
126
127
        $serverName = $request->server->get('SERVER_NAME') ?: $request->server->get('SERVER_ADDR');
128
        $response = new JsonResponse(['command' => $command->UUID, 'processed_by' => $serverName]);
0 ignored issues
show
Bug introduced by
Accessing UUID on the interface Surfnet\StepupMiddleware...gBundle\Command\Command suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
129
130
        $logger->notice(sprintf('Command "%s" has been successfully processed', $command));
131
132
        return $response;
133
    }
134
135
    /**
136
     * @param Command  $command
137
     * @param Metadata $metadata
138
     * @return string
139
     */
140
    private function resolveInstitution(Command $command, Metadata $metadata)
141
    {
142
        if ($metadata->actorInstitution) {
143
            return $metadata->actorInstitution;
144
        }
145
146
        // the createIdentityCommand is used to create an Identity for a new user,
147
        // the updateIdentityCommand is used to update name or email of an identity
148
        // Both are only sent by the SS when the Identity is not logged in yet,
149
        // thus there is not Metadata::actorInstitution,
150
        if ($command instanceof CreateIdentityCommand || $command instanceof UpdateIdentityCommand) {
151
            return $command->institution;
152
        }
153
154
        // conservative, if we cannot determine an institution, deny processing.
155
        throw new AccessDeniedHttpException(
156
            'Cannot reliably determine the institution of the actor, denying processing of command'
157
        );
158
    }
159
160
    /**
161
     * @param string      $institution
162
     * @param string|null $actorId
163
     */
164
    private function assertCommandMayBeProcessedOnBehalfOf($institution, $actorId)
165
    {
166
        if ($this->whitelistService->isWhitelisted($institution)) {
167
            return;
168
        }
169
170
        if (!$actorId) {
171
            throw new AccessDeniedHttpException(sprintf(
172
                'Institution "%s" is not on the whitelist and no actor is found, processing of command denied',
173
                $institution
174
            ));
175
        }
176
177
        if ($this->isSraa($actorId)) {
178
            return;
179
        }
180
181
        throw new AccessDeniedHttpException(sprintf(
182
            'Institution "%s" is not on the whitelist and actor "%s" is not an SRAA, processing of command denied',
183
            $institution,
184
            $actorId
185
        ));
186
    }
187
188
    private function assertRaaCommandMayBeExecuted($actorId, $actorInstitution, Command $command)
189
    {
190
        // Assert RAA specific authorizations
191
        if ($command instanceof RaExecutable) {
192
            if (!$actorId) {
193
                return;
194
            }
195
196
            if ($this->isSraa($actorId)) {
197
                return;
198
            }
199
200
            $raInstitution = $command->getRaInstitution();
201
            if (is_null($raInstitution)) {
202
                $raInstitution = $actorInstitution;
203
            }
204
205
            $authorizationContext = $this->institutionAuthorizationService->buildInstitutionAuthorizationContext(
206
                new IdentityId($actorId),
207
                InstitutionRole::useRaa()
208
            );
209
            if (!$authorizationContext->getInstitutions()->contains(new Institution($raInstitution))) {
210
                throw new AccessDeniedHttpException(sprintf(
211
                    'The actor "%s" is not allowed to act on behalf of institution  "%s" processing of command denied',
212
                    $actorId,
213
                    $raInstitution
214
                ));
215
            }
216
        }
217
    }
218
219
    private function isSraa($actorId)
220
    {
221
        $registrationAuthorityCredentials = $this->identityService->findRegistrationAuthorityCredentialsOf($actorId);
222
        if (!$registrationAuthorityCredentials) {
223
            return false;
224
        }
225
226
        if (!$registrationAuthorityCredentials->isSraa()) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $registrationAuth...yCredentials->isSraa();.
Loading history...
227
            return false;
228
        }
229
230
        return true;
231
    }
232
}
233