ConsumeAssertionService::getReceivedRequestId()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright 2018 SURFnet bv
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
namespace Surfnet\StepupGateway\SamlStepupProviderBundle\Service\Gateway;
19
20
use Exception;
21
use Psr\Log\LoggerInterface;
22
use Surfnet\SamlBundle\Http\PostBinding;
23
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
24
use Surfnet\StepupGateway\GatewayBundle\Entity\ServiceProvider;
25
use Surfnet\StepupGateway\GatewayBundle\Exception\ResponseFailureException;
26
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionAdapter;
27
use Surfnet\StepupGateway\GatewayBundle\Saml\Exception\UnknownInResponseToException;
28
use Surfnet\StepupGateway\SamlStepupProviderBundle\Exception\InvalidSubjectException;
29
use Surfnet\StepupGateway\SamlStepupProviderBundle\Exception\SecondfactorVerificationRequiredException;
30
use Surfnet\StepupGateway\SamlStepupProviderBundle\Provider\ConnectedServiceProviders;
31
use Surfnet\StepupGateway\SamlStepupProviderBundle\Provider\Provider;
32
use Surfnet\StepupGateway\SamlStepupProviderBundle\Saml\ProxyResponseFactory;
33
use Symfony\Component\HttpFoundation\Request;
34
35
/**
36
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
37
 */
38
class ConsumeAssertionService
39
{
40
    /** @var LoggerInterface */
41
    private $logger;
42
43
    /** @var SamlAuthenticationLogger */
44
    private $samlLogger;
45
46
    /** @var PostBinding */
47
    private $postBinding;
48
49
    /** @var ConnectedServiceProviders */
50
    private $connectedServiceProviders;
51
52
    private $handledRequestId = null;
53
54
    /**
55
     * ConsumeAssertionService constructor.
56
     * @param LoggerInterface $logger
57
     * @param SamlAuthenticationLogger $samlLogger
58
     * @param PostBinding $postBinding
59
     * @param ConnectedServiceProviders $connectedServiceProviders
60
     */
61
    public function __construct(
62
        LoggerInterface $logger,
63
        SamlAuthenticationLogger $samlLogger,
64
        PostBinding $postBinding,
65
        ConnectedServiceProviders $connectedServiceProviders
66
    ) {
67
        $this->logger = $logger;
68
        $this->samlLogger = $samlLogger;
69
        $this->postBinding = $postBinding;
70
        $this->connectedServiceProviders = $connectedServiceProviders;
71
    }
72
73
    /**
74
     * Process an assertion received from a remote GSSP application.
75
     *
76
     * There are two possible outcomes for a valid flow:
77
     *
78
     *  1. in case of registration: a SAMLResponse is returned
79
     *  2. in case of verification: a SecondfactorVerfificationRequiredException exception is thrown
80
     *
81
     * @param Provider $provider
82
     * @param Request $httpRequest
83
     * @param ProxyResponseFactory $proxyResponseFactory
84
     * @return \SAML2\Response
85
     * @throws Exception
86
     */
87
    public function consumeAssertion(Provider $provider, Request $httpRequest, ProxyResponseFactory $proxyResponseFactory)
88
    {
89
        $stateHandler = $provider->getStateHandler();
90
        $originalRequestId = $stateHandler->getRequestId();
91
92
        $this->handledRequestId = $originalRequestId;
93
94
        $logger = $this->samlLogger->forAuthentication($originalRequestId);
0 ignored issues
show
Bug introduced by
It seems like $originalRequestId can also be of type null; however, parameter $requestId of Surfnet\SamlBundle\Monol...er::forAuthentication() 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

94
        $logger = $this->samlLogger->forAuthentication(/** @scrutinizer ignore-type */ $originalRequestId);
Loading history...
95
96
        $action = $stateHandler->hasSubject() ? 'Second Factor Verification' : 'Proxy Response';
97
        $logger->notice(
98
            sprintf('Received SAMLResponse, attempting to process for %s', $action)
99
        );
100
101
        try {
102
            $assertion = $this->postBinding->processResponse(
103
                $httpRequest,
104
                $provider->getRemoteIdentityProvider(),
105
                $provider->getServiceProvider()
106
            );
107
        } catch (Exception $exception) {
108
            $message = sprintf('Could not process received Response, error: "%s"', $exception->getMessage());
109
            $logger->error($message);
110
            // Only pass along the original message back to the SP
111
            throw new ResponseFailureException($exception->getMessage());
112
        }
113
114
        $adaptedAssertion = new AssertionAdapter($assertion);
115
        $expectedResponse = $stateHandler->getGatewayRequestId();
116
        if (!$adaptedAssertion->inResponseToMatches($expectedResponse)) {
117
            throw new UnknownInResponseToException(
118
                $adaptedAssertion->getInResponseTo(),
119
                $expectedResponse
120
            );
121
        }
122
123
        $authenticatedNameId = $assertion->getNameId();
124
        $isSubjectRequested = $stateHandler->hasSubject();
125
        if ($isSubjectRequested && ($stateHandler->getSubject() !== $authenticatedNameId->getValue())) {
126
            $message = sprintf(
127
                'Requested Subject NameID "%s" and Response NameID "%s" do not match',
128
                $stateHandler->getSubject(),
129
                $authenticatedNameId->getValue()
130
            );
131
            $logger->critical($message);
132
133
            throw new InvalidSubjectException($message);
134
        }
135
136
        $logger->notice('Successfully processed SAMLResponse');
137
138
        if ($stateHandler->secondFactorVerificationRequested()) {
139
            $message = 'Second Factor verification was requested and was successful, forwarding to SecondFactor handling';
140
            $logger->notice($message);
141
142
            throw new SecondfactorVerificationRequiredException($message);
143
        }
144
145
        $targetServiceProvider = $this->getServiceProvider($stateHandler->getRequestServiceProvider());
146
147
        $response = $proxyResponseFactory->createProxyResponse(
148
            $assertion,
149
            $targetServiceProvider->determineAcsLocation(
150
                $stateHandler->getRequestAssertionConsumerServiceUrl(),
151
                $this->logger
152
            )
153
        );
154
155
        $logger->notice(sprintf(
156
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
157
            $stateHandler->getRequestId(),
158
            $response->getId()
159
        ));
160
161
        return $response;
162
    }
163
164
    /**
165
     * @param string $serviceProvider
166
     * @return ServiceProvider
167
     */
168
    private function getServiceProvider($serviceProvider)
169
    {
170
        return $this->connectedServiceProviders->getConfigurationOf($serviceProvider);
171
    }
172
173
    /**
174
     * @return null|string
175
     */
176
    public function getReceivedRequestId()
177
    {
178
        return $this->handledRequestId;
179
    }
180
}
181