Completed
Push — bugfix/enforce_raa ( 216a2a...11abae )
by
unknown
02:20
created

CommandController::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
rs 9.7
cc 1
nc 1
nop 7
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\Command\SelfServiceExecutable;
32
use Surfnet\StepupMiddleware\CommandHandlingBundle\EventSourcing\MetadataEnricher;
33
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\ForbiddenException;
34
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
35
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
36
use Surfnet\StepupMiddleware\CommandHandlingBundle\Pipeline\TransactionAwarePipeline;
37
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
38
use Symfony\Component\HttpFoundation\JsonResponse;
39
use Symfony\Component\HttpFoundation\Request;
40
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
41
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
42
43
/**
44
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
45
 */
46
class CommandController extends Controller
47
{
48
    /**
49
     * @var WhitelistService
50
     */
51
    private $whitelistService;
52
53
    /**
54
     * @var IdentityService
55
     */
56
    private $identityService;
57
58
    /**
59
     * @var TransactionAwarePipeline
60
     */
61
    private $pipeline;
62
63
    /**
64
     * @var MetadataEnricher
65
     */
66
    private $metadataEnricher;
67
68
    /**
69
     * @var AuthorizationChecker
70
     */
71
    private $authorizationChecker;
72
73
    /**
74
     * @var LoggerInterface
75
     */
76
    private $logger;
77
    /**
78
     * @var InstitutionAuthorizationService
79
     */
80
    private $institutionAuthorizationService;
81
82
    public function __construct(
83
        TransactionAwarePipeline $pipeline,
84
        WhitelistService $whitelistService,
85
        IdentityService $identityService,
86
        MetadataEnricher $enricher,
87
        AuthorizationChecker $authorizationChecker,
88
        LoggerInterface $logger,
89
        InstitutionAuthorizationService $institutionAuthorizationService
90
    ) {
91
        $this->pipeline = $pipeline;
92
        $this->whitelistService = $whitelistService;
93
        $this->identityService = $identityService;
94
        $this->authorizationChecker = $authorizationChecker;
95
        $this->metadataEnricher = $enricher;
96
        $this->logger = $logger;
97
        $this->institutionAuthorizationService = $institutionAuthorizationService;
98
    }
99
100
    public function handleAction(Command $command, Metadata $metadata, Request $request)
101
    {
102
        $this->denyAccessUnlessGranted(['ROLE_RA', 'ROLE_SS']);
103
104
        $logger = $this->logger;
105
        $logger->notice(sprintf('Received request to process Command "%s"', $command));
106
107
        $this->metadataEnricher->setMetadata($metadata);
108
109
        if ($this->authorizationChecker->isGranted('ROLE_MANAGEMENT')) {
110
            $logger->notice('Command sent through Management API, not enforcing Whitelist');
111
        } else {
112
            $logger->notice('Ensuring that the actor institution is on the whitelist, or the actor is SRAA');
113
114
            $institution = $this->resolveInstitution($command, $metadata);
115
            $this->assertInstitutionIsWhitelisted($institution, $metadata->actorId);
116
117
118
            $logger->notice('Ensuring that the actor is allowed to execute a command based on the fine grained authorization configuration');
119
120
            $this->assertSelfServiceCommandMayBeExecutedOnBehalfOf($metadata->actorId, $command);
121
            $this->assertRaCommandMayBeExecutedOnBehalfOf($metadata->actorId, $metadata->actorInstitution, $command);
122
123
            $logger->notice('Command authorization succeeded');
124
        }
125
126
        try {
127
            $command = $this->pipeline->process($command);
128
        } catch (ForbiddenException $e) {
129
            throw new AccessDeniedHttpException(
130
                sprintf('Processing of command "%s" is forbidden for this client', $command),
131
                $e
132
            );
133
        }
134
135
        $serverName = $request->server->get('SERVER_NAME') ?: $request->server->get('SERVER_ADDR');
136
        $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...
137
138
        $logger->notice(sprintf('Command "%s" has been successfully processed', $command));
139
140
        return $response;
141
    }
142
143
    /**
144
     * @param Command  $command
145
     * @param Metadata $metadata
146
     * @return string
147
     */
148
    private function resolveInstitution(Command $command, Metadata $metadata)
149
    {
150
        if ($metadata->actorInstitution) {
151
            return $metadata->actorInstitution;
152
        }
153
154
        // the createIdentityCommand is used to create an Identity for a new user,
155
        // the updateIdentityCommand is used to update name or email of an identity
156
        // Both are only sent by the SS when the Identity is not logged in yet,
157
        // thus there is not Metadata::actorInstitution,
158
        if ($command instanceof CreateIdentityCommand || $command instanceof UpdateIdentityCommand) {
159
            return $command->institution;
160
        }
161
162
        // conservative, if we cannot determine an institution, deny processing.
163
        throw new AccessDeniedHttpException(
164
            'Cannot reliably determine the institution of the actor, denying processing of command'
165
        );
166
    }
167
168
    /**
169
     * @param string      $institution
170
     * @param string|null $actorId
171
     */
172
    private function assertInstitutionIsWhitelisted($institution, $actorId)
173
    {
174
        if ($this->whitelistService->isWhitelisted($institution)) {
175
            return;
176
        }
177
178
        if (!$actorId) {
179
            throw new AccessDeniedHttpException(sprintf(
180
                'Institution "%s" is not on the whitelist and no actor is found, processing of command denied',
181
                $institution
182
            ));
183
        }
184
185
        if ($this->isSraa($actorId)) {
186
            return;
187
        }
188
189
        throw new AccessDeniedHttpException(sprintf(
190
            'Institution "%s" is not on the whitelist and actor "%s" is not an SRAA, processing of command denied',
191
            $institution,
192
            $actorId
193
        ));
194
    }
195
196
    /**
197
     * @param string $actorId
198
     * @param Command $command
199
     */
200
    private function assertSelfServiceCommandMayBeExecutedOnBehalfOf($actorId,  Command $command)
201
    {
202
        // Assert self service command could be executed
203
        if ($command instanceof SelfServiceExecutable) {
204
205
            $this->logger->notice('Asserting a SelfService command');
206
207
            // the createIdentityCommand is used to create an Identity for a new user,
208
            // the updateIdentityCommand is used to update name or email of an identity
209
            // Both are only sent by the SS when the Identity is not logged in yet,
210
            // thus there is not Metadata::actorInstitution,
211
            if ($command instanceof CreateIdentityCommand || $command instanceof UpdateIdentityCommand) {
212
                return;
213
            }
214
215
            // If the actor is SRAA all actions should be allowed
216
            if ($this->isSraa($actorId)) {
217
                return;
218
            }
219
220
            // Validate if the actor is the user
221
            if ($command->getIdentityId() !== $actorId) {
222
                throw new AccessDeniedHttpException(sprintf(
223
                    'The actor "%s" is not allowed to act on behalf of identity "%s" processing of command denied',
224
                    $actorId,
225
                    $command->getIdentityId()
226
                ));
227
            }
228
229
            return;
230
        }
231
    }
232
233
    /**
234
     * @param string $actorId
235
     * @param string $actorInstitution
236
     * @param Command $command
237
     */
238
    private function assertRaCommandMayBeExecutedOnBehalfOf($actorId, $actorInstitution, Command $command)
239
    {
240
        // Assert RAA specific authorizations
241
        if ($command instanceof RaExecutable) {
242
243
            $this->logger->notice('Asserting a RA command');
244
245
            // If the actor is SRAA all actions should be allowed
246
            if ($this->isSraa($actorId)) {
247
                return;
248
            }
249
250
            $raInstitution = $command->getRaInstitution();
251
            if (is_null($raInstitution)) {
252
                $raInstitution = $actorInstitution;
253
            }
254
255
            $authorizationContext = $this->institutionAuthorizationService->buildInstitutionAuthorizationContext(
256
                new IdentityId($actorId),
257
                InstitutionRole::useRaa()
258
            );
259
260
            if (!$authorizationContext->getInstitutions()->contains(new Institution($raInstitution))) {
261
                throw new AccessDeniedHttpException(sprintf(
262
                    'The actor "%s" is not allowed to act on behalf of institution  "%s" processing of command denied',
263
                    $actorId,
264
                    $raInstitution
265
                ));
266
            }
267
        }
268
    }
269
270
    /**
271
     * @param string $actorId
272
     * @return bool
273
     */
274
    private function isSraa($actorId)
275
    {
276
        $registrationAuthorityCredentials = $this->identityService->findRegistrationAuthorityCredentialsOf($actorId);
277
        if (!$registrationAuthorityCredentials) {
278
            return false;
279
        }
280
281
        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...
282
            return false;
283
        }
284
285
        return true;
286
    }
287
}
288