ProxyResponseService::updateNewAssertionWith()   B
last analyzed

Complexity

Conditions 7
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 4
nop 4
dl 0
loc 21
rs 8.8333
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
 */
18
19
namespace Surfnet\StepupGateway\GatewayBundle\Service;
20
21
use DateInterval;
22
use DateTime;
23
use DateTimeZone;
24
use SAML2\Assertion;
25
use SAML2\Constants;
26
use SAML2\Response;
27
use SAML2\XML\saml\Issuer;
28
use SAML2\XML\saml\SubjectConfirmation;
29
use SAML2\XML\saml\SubjectConfirmationData;
30
use Surfnet\SamlBundle\Entity\IdentityProvider;
31
use Surfnet\SamlBundle\SAML2\Attribute\AttributeDefinition;
32
use Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary;
33
use Surfnet\StepupBundle\Value\Loa;
34
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
35
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionSigningService;
36
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler;
37
38
/**
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
40
 */
41
class ProxyResponseService
42
{
43
    /**
44
     * @var \Surfnet\SamlBundle\Entity\IdentityProvider
45
     */
46
    private $hostedIdentityProvider;
47
48
    /**
49
     * @var \Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler
50
     */
51
    private $proxyStateHandler;
52
53
    /**
54
     * @var \Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary
55
     */
56
    private $attributeDictionary;
57
58
    /**
59
     * @var \Surfnet\SamlBundle\SAML2\Attribute\AttributeDefinition
60
     */
61
    private $internalCollabPersonIdAttribute;
62
63
    /**
64
     * @var \DateTime
65
     */
66
    private $currentTime;
67
68
    /**
69
     * @var \Surfnet\StepupGateway\GatewayBundle\Saml\AssertionSigningService
70
     */
71
    private $assertionSigningService;
72
73
    /**
74
     * @var \Surfnet\StepupBundle\Value\Loa
75
     */
76
    private $intrinsicLoa;
77
78
    public function __construct(
79
        IdentityProvider        $hostedIdentityProvider,
80
        ProxyStateHandler       $proxyStateHandler,
81
        AssertionSigningService $assertionSigningService,
82
        AttributeDictionary     $attributeDictionary,
83
        AttributeDefinition     $internalCollabPersonIdAttribute,
84
        Loa                     $intrinsicLoa,
85
        DateTime                $now = null
86
    ) {
87
        $this->hostedIdentityProvider = $hostedIdentityProvider;
88
        $this->proxyStateHandler = $proxyStateHandler;
89
        $this->assertionSigningService = $assertionSigningService;
90
        $this->attributeDictionary = $attributeDictionary;
91
        $this->internalCollabPersonIdAttribute = $internalCollabPersonIdAttribute;
92
        $this->intrinsicLoa = $intrinsicLoa;
93
        $this->currentTime = is_null($now) ? new DateTime('now', new DateTimeZone('UTC')) : $now;
94
    }
95
96
    /**
97
     * @param Assertion $assertion
98
     * @param string $destination ACS URL
99
     * @param string|null $loa
100
     * @return Response
101
     */
102
    public function createProxyResponse(Assertion $assertion, $destination, $loa = null)
103
    {
104
        $newAssertion = new Assertion();
105
        $newAssertion->setNotBefore($this->currentTime->getTimestamp());
106
        $newAssertion->setNotOnOrAfter($this->getTimestamp('PT5M'));
107
        $newAssertion->setAttributes($assertion->getAttributes());
108
        $issuerVo = new Issuer();
109
        $issuerVo->setValue($this->hostedIdentityProvider->getEntityId());
0 ignored issues
show
Bug introduced by
It seems like $this->hostedIdentityProvider->getEntityId() can also be of type null; however, parameter $value of SAML2\XML\saml\NameIDType::setValue() 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

109
        $issuerVo->setValue(/** @scrutinizer ignore-type */ $this->hostedIdentityProvider->getEntityId());
Loading history...
110
        $newAssertion->setIssuer($issuerVo);
111
        $newAssertion->setIssueInstant($this->getTimestamp());
112
113
        $this->assertionSigningService->signAssertion($newAssertion);
114
        $this->addSubjectConfirmationFor($newAssertion, $destination);
115
        $translatedAssertion = $this->attributeDictionary->translate($assertion);
116
        $eptiNameId = $translatedAssertion->getAttributeValue('eduPersonTargetedID');
117
        $internalCollabPersonId = $translatedAssertion->getAttributeValue('internalCollabPersonId');
118
        // Perform some input validation on the eptiNameId and/or internal-collabPersonId that was received.
119
        if (is_null($eptiNameId) && is_null($internalCollabPersonId)) {
120
            throw new RuntimeException(
121
                'Neither "urn:mace:dir:attribute-def:eduPersonTargetedID" nor ' .
122
                '"urn:mace:surf.nl:attribute-def:internal-collabPersonId" is present'
123
            );
124
        }
125
        $this->updateNewAssertionWith($eptiNameId, $internalCollabPersonId, $newAssertion, $assertion);
126
        $newAssertion->setValidAudiences([$this->proxyStateHandler->getRequestServiceProvider()]);
127
128
        $this->addAuthenticationStatementTo($newAssertion, $assertion);
129
130
        if ($loa) {
131
            $newAssertion->setAuthnContextClassRef($loa);
132
        }
133
134
        return $this->createNewAuthnResponse($newAssertion, $destination);
135
    }
136
137
    /**
138
     * @param Assertion $newAssertion
139
     * @param string $destination ACS URL
140
     */
141
    private function addSubjectConfirmationFor(Assertion $newAssertion, $destination): void
142
    {
143
        $confirmation = new SubjectConfirmation();
144
        $confirmation->setMethod(Constants::CM_BEARER);
145
146
        $confirmationData = new SubjectConfirmationData();
147
        $confirmationData->setInResponseTo($this->proxyStateHandler->getRequestId());
148
        $confirmationData->setRecipient($destination);
149
        $confirmationData->setNotOnOrAfter($newAssertion->getNotOnOrAfter());
150
151
        $confirmation->setSubjectConfirmationData($confirmationData);
152
153
        $newAssertion->setSubjectConfirmation([$confirmation]);
154
    }
155
156
    /**
157
     * @param Assertion $newAssertion
158
     * @param Assertion $assertion
159
     */
160
    private function addAuthenticationStatementTo(Assertion $newAssertion, Assertion $assertion): void
161
    {
162
        $newAssertion->setAuthnInstant($assertion->getAuthnInstant());
163
        $newAssertion->setAuthnContextClassRef((string)$this->intrinsicLoa);
164
165
        $authority = $assertion->getAuthenticatingAuthority();
166
        $newAssertion->setAuthenticatingAuthority(
167
            array_merge(
168
                (empty($authority) ? [] : $authority),
169
                [$assertion->getIssuer()->getValue()]
170
            )
171
        );
172
    }
173
174
    /**
175
     * @param Assertion $newAssertion
176
     * @param string $destination ACS URL
177
     * @return Response
178
     */
179
    private function createNewAuthnResponse(Assertion $newAssertion, $destination)
180
    {
181
        $response = new Response();
182
        $response->setAssertions([$newAssertion]);
183
        $issuerVo = new Issuer();
184
        $issuerVo->setValue($this->hostedIdentityProvider->getEntityId());
0 ignored issues
show
Bug introduced by
It seems like $this->hostedIdentityProvider->getEntityId() can also be of type null; however, parameter $value of SAML2\XML\saml\NameIDType::setValue() 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

184
        $issuerVo->setValue(/** @scrutinizer ignore-type */ $this->hostedIdentityProvider->getEntityId());
Loading history...
185
        $response->setIssuer($issuerVo);
186
        $response->setIssueInstant($this->getTimestamp());
187
        $response->setDestination($destination);
188
        $response->setInResponseTo($this->proxyStateHandler->getRequestId());
189
190
        return $response;
191
    }
192
193
    /**
194
     * @param string|null $interval a \DateInterval compatible interval to skew the time with
195
     *
196
     * @return int
197
     * @throws \Exception
198
     */
199
    private function getTimestamp($interval = null)
200
    {
201
        $time = clone $this->currentTime;
202
203
        if ($interval) {
204
            $time->add(new DateInterval($interval));
205
        }
206
207
        return $time->getTimestamp();
208
    }
209
210
    private function updateNewAssertionWith(
211
        $eptiNameId,
212
        $internalCollabPersonId,
213
        Assertion $newAssertion,
214
        Assertion $originalAssertion
215
    ) :void {
216
        if (!$internalCollabPersonId && $eptiNameId) {
217
            if (is_null($internalCollabPersonId) && (!array_key_exists(0, $eptiNameId) || !$eptiNameId[0]->getValue())) {
218
                throw new RuntimeException(
219
                    'The "urn:mace:dir:attribute-def:eduPersonTargetedID" attribute does not contain a NameID ' .
220
                    'with a value.'
221
                );
222
            }
223
            $newAssertion->setNameId($eptiNameId[0]);
224
        } elseif ($internalCollabPersonId) {
225
            // Remove the internal-collabPersonId from the assertion
226
            $attributes = $newAssertion->getAttributes();
227
            unset($attributes[$this->internalCollabPersonIdAttribute->getUrnMace()]);
228
            $newAssertion->setAttributes($attributes);
229
            // Use the supplied NameID as the NameID to the SP
230
            $newAssertion->setNameId($originalAssertion->getNameId());
231
        }
232
    }
233
}
234