Completed
Push — feature/pdp-cbac ( e99222 )
by
unknown
13:10
created

PdpService   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 13
c 0
b 0
f 0
lcom 1
cbo 8
dl 0
loc 153
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A isEnabledForSpOrIdp() 0 11 3
B enforceObligatoryLoa() 0 53 5
B findHighestObligatoryLoa() 0 22 4
1
<?php
2
3
/**
4
 * Copyright 2017 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\StepupGateway\GatewayBundle\Service;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\StepupBundle\Service\LoaResolutionService;
23
use Surfnet\StepupBundle\Value\Loa;
24
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
25
use Surfnet\StepupGateway\GatewayBundle\Pdp\Dto\Request;
26
use Surfnet\StepupGateway\GatewayBundle\Pdp\Dto\Response;
27
use Surfnet\StepupGateway\GatewayBundle\Pdp\PdpClientInterface;
28
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
29
30
/**
31
 * Call the Policy Decision Point API.
32
 */
33
final class PdpService
34
{
35
    /**
36
     * @var PdpClientInterface
37
     */
38
    private $client;
39
40
    /**
41
     * @var LoaResolutionService
42
     */
43
    private $loaResolutionService;
44
45
    /**
46
     * @var \Psr\Log\LoggerInterface
47
     */
48
    private $logger;
49
50
    /**
51
     * Identifier with which to identify stepup to the PDP endpoint.
52
     *
53
     * @var string
54
     */
55
    private $clientId;
56
57
    public function __construct(PdpClientInterface $client, LoaResolutionService $loaResolutionService, LoggerInterface $logger, $clientId)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 139 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
58
    {
59
        $this->client = $client;
60
        $this->loaResolutionService = $loaResolutionService;
61
        $this->logger = $logger;
62
        $this->clientId = $clientId;
63
    }
64
65
    /**
66
     * Check if PDP is enabled for given the SP or IdP in current context.
67
     *
68
     * @param ResponseContext $context
69
     * @return bool
70
     */
71
    public function isEnabledForSpOrIdp(ResponseContext $context)
72
    {
73
        $idp = $context->getAuthenticatingIdp();
74
        $sp = $context->getServiceProvider();
75
76
        if ($idp && $idp->pdpEnabled()) {
77
            return true;
78
        }
79
80
        return $sp->pdpEnabled();
81
    }
82
83
    /**
84
     * Call the PDP endpoint and determine the LoA obligated by the policy decision.
85
     *
86
     * This method takes the original LoA required by the SP or IdP and
87
     * returns either the same LoA, or a higher LoA of a higher LoA is
88
     * obligated by the PDP endpoint.
89
     *
90
     * A policy decision can result in one of four situations:
91
     *
92
     *  - access was denied (denied, indeterminate)
93
     *  - permit, without obligatory LoA
94
     *  - permit, with obligatory LoA lower than or equal to original required LoA -> original required LoA unaffectd
95
     *  - permit, with obligatory LoA higher than or original required LoA -> required LoA increased
96
     *
97
     * @param Loa $originalRequiredLoa
98
     * @param string $subjectId
99
     * @param string $idpEntityId
100
     * @param string $spEntityId
101
     * @param array $attributes
102
     * @param string $clientIp
103
     * @return Loa
0 ignored issues
show
Documentation introduced by
Should the return type not be Loa|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
104
     */
105
    public function enforceObligatoryLoa(Loa $originalRequiredLoa, $subjectId, $idpEntityId, $spEntityId, array $attributes, $clientIp)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
106
    {
107
        $policyDecision = $this->client->requestDecisionFor(
108
            Request::from($this->clientId, $subjectId, $idpEntityId, $spEntityId, $attributes, $clientIp)
109
        );
110
111
        if (!$policyDecision->permitsAccess()) {
112
            throw new RuntimeException(
113
                sprintf(
114
                    'The policy decision point (PDP) denied access (%s)',
115
                    $policyDecision->getFormattedStatus()
116
                )
117
            );
118
        }
119
120
        $newRequiredLoa = $originalRequiredLoa;
121
122
        if ($policyDecision->hasLoaObligations()) {
123
            $loaRequiredByPolicyDecision = $this->findHighestObligatoryLoa(
124
                $policyDecision->getLoaObligations()
125
            );
126
127
            if ($loaRequiredByPolicyDecision->equals($originalRequiredLoa)) {
128
                $this->logger->info(
129
                    sprintf(
130
                        'The policy decision point (PDP) sent an obligation for LoA %s, which matches the LoA already required.',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
131
                        $loaRequiredByPolicyDecision
132
                    )
133
                );
134
            } elseif ($loaRequiredByPolicyDecision->canSatisfyLoa($originalRequiredLoa)) {
135
                $newRequiredLoa = $loaRequiredByPolicyDecision;
136
137
                $this->logger->info(
138
                    sprintf(
139
                        'The policy decision point (PDP) sent an obligation for LoA %s, updating required LoA from %s to %s.',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
140
                        $loaRequiredByPolicyDecision,
141
                        $originalRequiredLoa,
142
                        $loaRequiredByPolicyDecision
143
                    )
144
                );
145
            } else {
146
                $this->logger->info(
147
                    sprintf(
148
                        'The policy decision point (PDP) sent an obligation for LoA %s, but required LoA %s is higher - PDP has no effect.',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 140 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
149
                        $loaRequiredByPolicyDecision,
150
                        $originalRequiredLoa
151
                    )
152
                );
153
            }
154
        }
155
156
        return $newRequiredLoa;
157
    }
158
159
    /**
160
     * @param string[] $uris List of LoA URIs
161
     * @return Loa           The highest LoA found in the policy decision obligations
0 ignored issues
show
Documentation introduced by
Should the return type not be Loa|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
162
     */
163
    private function findHighestObligatoryLoa(array $uris)
164
    {
165
        $highestObligatedLoa = $this->loaResolutionService->getLoaByLevel(Loa::LOA_1);
166
167
        foreach ($uris as $uri) {
168
            if (!$this->loaResolutionService->hasLoa($uri)) {
169
                throw new RuntimeException(
170
                    sprintf(
171
                        'The policy decision point (PDP) obligates LoA %s - but that LoA is not supported in the StepUp configuration',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
172
                        $uri
173
                    )
174
                );
175
            }
176
177
            $loa = $this->loaResolutionService->getLoa($uri);
178
            if ($loa->canSatisfyLoa($highestObligatedLoa)) {
0 ignored issues
show
Bug introduced by
It seems like $highestObligatedLoa can be null; however, canSatisfyLoa() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
179
                $highestObligatedLoa = $loa;
180
            }
181
        }
182
183
        return $highestObligatedLoa;
184
    }
185
}
186