ResponseFactory   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 55
c 0
b 0
f 0
dl 0
loc 141
rs 10
wmc 9

7 Methods

Rating   Name   Duplication   Size   Complexity  
A createNewAuthnResponse() 0 12 1
A getTimestamp() 0 9 2
A __construct() 0 10 2
A addAuthenticationStatementTo() 0 5 1
A createSecondFactorOnlyResponse() 0 9 1
A createNewAssertion() 0 19 1
A addSubjectConfirmationFor() 0 13 1
1
<?php
2
3
/**
4
 * Copyright 2016 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\SecondFactorOnlyBundle\Saml;
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\NameID;
29
use SAML2\XML\saml\SubjectConfirmation;
30
use SAML2\XML\saml\SubjectConfirmationData;
31
use Surfnet\SamlBundle\Entity\IdentityProvider;
32
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionSigningService;
33
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler;
34
35
/**
36
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
37
 */
38
final class ResponseFactory
39
{
40
    /**
41
     * @var \Surfnet\SamlBundle\Entity\IdentityProvider
42
     */
43
    private $hostedIdentityProvider;
44
45
    /**
46
     * @var \Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler
47
     */
48
    private $proxyStateHandler;
49
50
    /**
51
     * @var \DateTime
52
     */
53
    private $currentTime;
54
55
    /**
56
     * @var \Surfnet\StepupGateway\GatewayBundle\Saml\AssertionSigningService
57
     */
58
    private $assertionSigningService;
59
60
    public function __construct(
61
        IdentityProvider $hostedIdentityProvider,
62
        ProxyStateHandler $proxyStateHandler,
63
        AssertionSigningService $assertionSigningService,
64
        DateTime $now = null
65
    ) {
66
        $this->hostedIdentityProvider    = $hostedIdentityProvider;
67
        $this->proxyStateHandler         = $proxyStateHandler;
68
        $this->assertionSigningService   = $assertionSigningService;
69
        $this->currentTime = is_null($now) ? new DateTime('now', new DateTimeZone('UTC')): $now;
70
    }
71
72
    /**
73
     * @param string $nameId
74
     * @param string $destination
75
     * @param string|null $authnContextClassRef
76
     * @return Response
77
     */
78
    public function createSecondFactorOnlyResponse($nameId, $destination, $authnContextClassRef)
79
    {
80
        return $this->createNewAuthnResponse(
81
            $this->createNewAssertion(
82
                $nameId,
83
                $authnContextClassRef,
84
                $destination
85
            ),
86
            $destination
87
        );
88
    }
89
90
    /**
91
     * @param Assertion $newAssertion
92
     * @param string $destination The ACS location
93
     * @return Response
94
     */
95
    private function createNewAuthnResponse(Assertion $newAssertion, $destination)
96
    {
97
        $response = new Response();
98
        $response->setAssertions([$newAssertion]);
99
        $issuerVo = new Issuer();
100
        $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

100
        $issuerVo->setValue(/** @scrutinizer ignore-type */ $this->hostedIdentityProvider->getEntityId());
Loading history...
101
        $response->setIssuer($issuerVo);
102
        $response->setIssueInstant($this->getTimestamp());
103
        $response->setDestination($destination);
104
        $response->setInResponseTo($this->proxyStateHandler->getRequestId());
105
106
        return $response;
107
    }
108
109
    /**
110
     * @param string $nameId
111
     * @param string $authnContextClassRef
112
     * @param string $destination The ACS location
113
     * @return Assertion
114
     */
115
    private function createNewAssertion($nameId, $authnContextClassRef, $destination)
116
    {
117
        $newAssertion = new Assertion();
118
        $newAssertion->setNotBefore($this->currentTime->getTimestamp());
119
        $newAssertion->setNotOnOrAfter($this->getTimestamp('PT5M'));
120
        $issuer = new Issuer();
121
        $issuer->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

121
        $issuer->setValue(/** @scrutinizer ignore-type */ $this->hostedIdentityProvider->getEntityId());
Loading history...
122
        $newAssertion->setIssuer($issuer);
123
        $newAssertion->setIssueInstant($this->getTimestamp());
124
        $this->assertionSigningService->signAssertion($newAssertion);
125
        $this->addSubjectConfirmationFor($newAssertion, $destination);
126
        $nameIdVo = new NameID();
127
        $nameIdVo->setValue($nameId);
128
        $nameIdVo->setFormat(Constants::NAMEID_UNSPECIFIED);
129
        $newAssertion->setNameId($nameIdVo);
130
        $newAssertion->setValidAudiences([$this->proxyStateHandler->getRequestServiceProvider()]);
131
        $this->addAuthenticationStatementTo($newAssertion, $authnContextClassRef);
132
133
        return $newAssertion;
134
    }
135
136
    /**
137
     * @param Assertion $newAssertion
138
     * @param string $destination The ACS location
139
     */
140
    private function addSubjectConfirmationFor(Assertion $newAssertion, $destination): void
141
    {
142
        $confirmation = new SubjectConfirmation();
143
        $confirmation->setMethod(Constants::CM_BEARER);
144
145
        $confirmationData = new SubjectConfirmationData();
146
        $confirmationData->setInResponseTo($this->proxyStateHandler->getRequestId());
147
        $confirmationData->setRecipient($destination);
148
        $confirmationData->setNotOnOrAfter($newAssertion->getNotOnOrAfter());
149
150
        $confirmation->setSubjectConfirmationData($confirmationData);
151
152
        $newAssertion->setSubjectConfirmation([$confirmation]);
153
    }
154
155
    /**
156
     * @param Assertion $assertion
157
     * @param $authnContextClassRef
158
     */
159
    private function addAuthenticationStatementTo(Assertion $assertion, $authnContextClassRef): void
160
    {
161
        $assertion->setAuthnInstant($this->getTimestamp());
162
        $assertion->setAuthnContextClassRef($authnContextClassRef);
163
        $assertion->setAuthenticatingAuthority([$this->hostedIdentityProvider->getEntityId()]);
164
    }
165
166
    /**
167
     * @param string|null $interval a \DateInterval compatible interval to skew the time with
168
     * @return int
169
     */
170
    private function getTimestamp($interval = null)
171
    {
172
        $time = clone $this->currentTime;
173
174
        if ($interval) {
175
            $time->add(new DateInterval($interval));
176
        }
177
178
        return $time->getTimestamp();
179
    }
180
}
181