Completed
Push — feature/implement-state-handli... ( d0163b...537647 )
by Michiel
01:51
created

ConsumeAssertionService::getReceivedRequestId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Surfnet\StepupGateway\SamlStepupProviderBundle\Service\Gateway;
4
5
use Exception;
6
use Psr\Log\LoggerInterface;
7
use Surfnet\SamlBundle\Http\PostBinding;
8
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
9
use Surfnet\StepupGateway\GatewayBundle\Entity\ServiceProvider;
10
use Surfnet\StepupGateway\GatewayBundle\Exception\ResponseFailureException;
11
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionAdapter;
12
use Surfnet\StepupGateway\GatewayBundle\Saml\Exception\UnknownInResponseToException;
13
use Surfnet\StepupGateway\SamlStepupProviderBundle\Exception\InvalidSubjectException;
14
use Surfnet\StepupGateway\SamlStepupProviderBundle\Exception\SecondfactorVerfificationRequiredException;
15
use Surfnet\StepupGateway\SamlStepupProviderBundle\Provider\ConnectedServiceProviders;
16
use Surfnet\StepupGateway\SamlStepupProviderBundle\Provider\Provider;
17
use Surfnet\StepupGateway\SamlStepupProviderBundle\Saml\ProxyResponseFactory;
18
use Symfony\Component\HttpFoundation\Request;
19
20
/**
21
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
22
 */
23
class ConsumeAssertionService
24
{
25
    /** @var LoggerInterface */
26
    private $logger;
27
28
    /** @var SamlAuthenticationLogger */
29
    private $samlLogger;
30
31
    /** @var PostBinding */
32
    private $postBinding;
33
34
    /** @var ConnectedServiceProviders */
35
    private $connectedServiceProviders;
36
37
    private $handledRequestId = null;
38
39
    /**
40
     * ConsumeAssertionService constructor.
41
     * @param LoggerInterface $logger
42
     * @param SamlAuthenticationLogger $samlLogger
43
     * @param PostBinding $postBinding
44
     * @param ConnectedServiceProviders $connectedServiceProviders
45
     */
46
    public function __construct(
47
        LoggerInterface $logger,
48
        SamlAuthenticationLogger $samlLogger,
49
        PostBinding $postBinding,
50
        ConnectedServiceProviders $connectedServiceProviders
51
    ) {
52
        $this->logger = $logger;
53
        $this->samlLogger = $samlLogger;
54
        $this->postBinding = $postBinding;
55
        $this->connectedServiceProviders = $connectedServiceProviders;
56
    }
57
58
    /**
59
     * Process an assertion received from a remote GSSP application.
60
     *
61
     * There are two possible outcomes for a valid flow:
62
     *
63
     *  1. in case of registration: a SAMLResponse is returned
64
     *  2. in case of verification: a SecondfactorVerfificationRequiredException exception is thrown
65
     *
66
     * @param Provider $provider
67
     * @param Request $httpRequest
68
     * @param ProxyResponseFactory $proxyResponseFactory
69
     * @return \SAML2\Response
70
     * @throws Exception
71
     */
72
    public function consumeAssertion(Provider $provider, Request $httpRequest, ProxyResponseFactory $proxyResponseFactory)
73
    {
74
        $stateHandler = $provider->getStateHandler();
75
        $originalRequestId = $stateHandler->getRequestId();
76
77
        $this->handledRequestId = $originalRequestId;
78
79
        $logger = $this->samlLogger->forAuthentication($originalRequestId);
80
81
        $action = $stateHandler->hasSubject() ? 'Second Factor Verification' : 'Proxy Response';
82
        $logger->notice(
83
            sprintf('Received SAMLResponse, attempting to process for %s', $action)
84
        );
85
86
        try {
87
            $assertion = $this->postBinding->processResponse(
88
                $httpRequest,
89
                $provider->getRemoteIdentityProvider(),
90
                $provider->getServiceProvider()
91
            );
92
        } catch (Exception $exception) {
93
            $message = sprintf('Could not process received Response, error: "%s"', $exception->getMessage());
94
            $logger->error($message);
95
96
            throw new ResponseFailureException($message);
97
        }
98
99
        $adaptedAssertion = new AssertionAdapter($assertion);
100
        $expectedResponse = $stateHandler->getGatewayRequestId();
101
        if (!$adaptedAssertion->inResponseToMatches($expectedResponse)) {
102
            throw new UnknownInResponseToException(
103
                $adaptedAssertion->getInResponseTo(),
104
                $expectedResponse
105
            );
106
        }
107
108
        $authenticatedNameId = $assertion->getNameId();
109
        $isSubjectRequested = $stateHandler->hasSubject();
110
        if ($isSubjectRequested && ($stateHandler->getSubject() !== $authenticatedNameId->value)) {
111
            $message = sprintf(
112
                'Requested Subject NameID "%s" and Response NameID "%s" do not match',
113
                $stateHandler->getSubject(),
114
                $authenticatedNameId->value
115
            );
116
            $logger->critical($message);
117
118
            throw new InvalidSubjectException($message);
119
        }
120
121
        $logger->notice('Successfully processed SAMLResponse');
122
123
        if ($stateHandler->secondFactorVerificationRequested()) {
124
            $message = 'Second Factor verification was requested and was successful, forwarding to SecondFactor handling';
125
            $logger->notice($message);
126
127
            throw new SecondfactorVerfificationRequiredException($message);
128
        }
129
130
        $targetServiceProvider = $this->getServiceProvider($stateHandler->getRequestServiceProvider());
131
132
        $response = $proxyResponseFactory->createProxyResponse(
133
            $assertion,
134
            $targetServiceProvider->determineAcsLocation(
135
                $stateHandler->getRequestAssertionConsumerServiceUrl(),
136
                $this->logger
137
            )
138
        );
139
140
        $logger->notice(sprintf(
141
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
142
            $stateHandler->getRequestId(),
143
            $response->getId()
144
        ));
145
146
        return $response;
147
    }
148
149
    /**
150
     * @param string $serviceProvider
151
     * @return ServiceProvider
152
     */
153
    private function getServiceProvider($serviceProvider)
154
    {
155
        return $this->connectedServiceProviders->getConfigurationOf($serviceProvider);
156
    }
157
158
    /**
159
     * @return null|string
160
     */
161
    public function getReceivedRequestId()
162
    {
163
        return $this->handledRequestId;
164
    }
165
}
166