Completed
Push — develop ( 267d20...95c972 )
by Boy
18:51 queued 15:33
created

SamlListener   B

Complexity

Total Complexity 19

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 5
Bugs 0 Features 2
Metric Value
wmc 19
c 5
b 0
f 2
lcom 1
cbo 17
dl 0
loc 162
rs 7.8571

8 Methods

Rating   Name   Duplication   Size   Complexity  
A renderPreconditionExceptionResponse() 0 14 4
A getTokenStorage() 0 4 1
C handleEvent() 0 85 8
A renderBadCredentialsResponse() 0 7 1
A renderAuthenticationExceptionResponse() 0 7 1
A renderTemplate() 0 8 1
A __construct() 0 4 1
A handle() 0 10 2
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\StepupRa\RaBundle\Security\Firewall;
20
21
use Exception;
22
use SAML2_Response_Exception_PreconditionNotMetException as PreconditionNotMetException;
23
use Surfnet\SamlBundle\Http\Exception\AuthnFailedSamlResponseException;
24
use Surfnet\SamlBundle\Http\Exception\NoAuthnContextSamlResponseException;
25
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
26
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
27
use Surfnet\StepupRa\RaBundle\Security\Authentication\SamlInteractionProvider;
28
use Surfnet\StepupRa\RaBundle\Security\Authentication\SessionHandler;
29
use Surfnet\StepupRa\RaBundle\Security\Authentication\Token\SamlToken;
30
use Surfnet\StepupRa\RaBundle\Security\Exception\UnmetLoaException;
31
use Symfony\Component\DependencyInjection\ContainerInterface;
32
use Symfony\Component\HttpFoundation\RedirectResponse;
33
use Symfony\Component\HttpFoundation\Response;
34
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
35
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
36
use Symfony\Component\Security\Core\Exception\AuthenticationException;
37
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
38
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
39
use Twig_Environment as Twig;
40
41
/**
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 */
44
class SamlListener implements ListenerInterface
45
{
46
    /**
47
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
48
     */
49
    private $container;
50
51
    public function __construct(ContainerInterface $container)
52
    {
53
        $this->container = $container;
54
    }
55
56
    public function handle(GetResponseEvent $event)
57
    {
58
        try {
59
            $this->handleEvent($event);
60
        } catch (\Exception $e) {
61
            /** @var SamlInteractionProvider $samlInteractionProvider */
62
            $samlInteractionProvider = $this->container->get('ra.security.authentication.saml');
63
            $samlInteractionProvider->reset();
64
        }
65
    }
66
67
    private function handleEvent(GetResponseEvent $event)
68
    {
69
        /** @var SessionHandler $sessionHandler */
70
        $sessionHandler = $this->container->get('ra.security.authentication.session_handler');
71
72
        // reinstate the token from the session. Could be expanded with logout check if needed
73
        if ($this->getTokenStorage()->getToken()) {
74
            return;
75
        }
76
77
        /** @var SamlInteractionProvider $samlInteractionProvider */
78
        $samlInteractionProvider = $this->container->get('ra.security.authentication.saml');
79
80
        if (!$samlInteractionProvider->isSamlAuthenticationInitiated()) {
81
            $sessionHandler->setCurrentRequestUri($event->getRequest()->getUri());
82
            $event->setResponse($samlInteractionProvider->initiateSamlRequest());
83
84
            /** @var SamlAuthenticationLogger $logger */
85
            $logger = $this->container->get('surfnet_saml.logger')->forAuthentication($sessionHandler->getRequestId());
86
            $logger->notice('Sending AuthnRequest');
87
88
            return;
89
        }
90
91
        /** @var SamlAuthenticationLogger $logger */
92
        $logger = $this->container->get('surfnet_saml.logger')->forAuthentication($sessionHandler->getRequestId());
93
        $expectedInResponseTo = $sessionHandler->getRequestId();
94
        try {
95
            $assertion = $samlInteractionProvider->processSamlResponse($event->getRequest());
96
        } catch (PreconditionNotMetException $e) {
97
            $logger->notice(sprintf('SAML response precondition not met: "%s"', $e->getMessage()));
98
            $event->setResponse($this->renderPreconditionExceptionResponse($e));
99
            return;
100
        } catch (Exception $e) {
101
            $logger->error(sprintf('Failed SAMLResponse Parsing: "%s"', $e->getMessage()));
102
            throw new AuthenticationException('Failed SAMLResponse parsing', 0, $e);
103
        }
104
105
        if (!InResponseTo::assertEquals($assertion, $expectedInResponseTo)) {
106
            $logger->error('Unknown or unexpected InResponseTo in SAMLResponse');
107
108
            throw new AuthenticationException('Unknown or unexpected InResponseTo in SAMLResponse');
109
        }
110
111
        $logger->notice('Successfully processed SAMLResponse, attempting to authenticate');
112
113
        $loaResolutionService = $this->container->get('surfnet_stepup.service.loa_resolution');
114
        $loa = $loaResolutionService->getLoa($assertion->getAuthnContextClassRef());
115
116
        $token = new SamlToken($loa);
117
        $token->assertion = $assertion;
118
119
        /** @var AuthenticationProviderManager $authenticationManager */
120
        $authenticationManager = $this->container->get('security.authentication.manager');
121
122
        try {
123
            $authToken = $authenticationManager->authenticate($token);
124
        } catch (BadCredentialsException $exception) {
125
            $logger->error(
126
                sprintf('Bad credentials, reason: "%s"', $exception->getMessage()),
127
                ['exception' => $exception]
128
            );
129
130
            $event->setResponse($this->renderBadCredentialsResponse($exception));
131
            return;
132
        } catch (AuthenticationException $failed) {
133
            $logger->error(
134
                sprintf('Authentication Failed, reason: "%s"', $failed->getMessage()),
135
                ['exception' => $failed]
136
            );
137
138
            $event->setResponse($this->renderAuthenticationExceptionResponse($failed));
139
            return;
140
        }
141
142
        // for the current request
143
        $this->getTokenStorage()->setToken($authToken);
144
145
        // migrate the session to prevent session hijacking
146
        $sessionHandler->migrate();
147
148
        $event->setResponse(new RedirectResponse($sessionHandler->getCurrentRequestUri()));
149
150
        $logger->notice('Authentication succeeded, redirecting to original location');
151
    }
152
153
    private function renderPreconditionExceptionResponse(PreconditionNotMetException $exception)
154
    {
155
        if ($exception instanceof AuthnFailedSamlResponseException) {
156
            $template = 'SurfnetStepupRaRaBundle:Saml/Exception:authnFailed.html.twig';
157
        } elseif ($exception instanceof NoAuthnContextSamlResponseException) {
158
            $template = 'SurfnetStepupRaRaBundle:Saml/Exception:noAuthnContext.html.twig';
159
        } elseif ($exception instanceof UnmetLoaException) {
160
            $template = 'SurfnetStepupRaRaBundle:Saml/Exception:unmetLoa.html.twig';
161
        } else {
162
            $template = 'SurfnetStepupRaRaBundle:Saml/Exception:preconditionNotMet.html.twig';
163
        }
164
165
        return $this->renderTemplate($template, ['exception' => $exception]);
166
    }
167
168
    private function renderBadCredentialsResponse(BadCredentialsException $exception)
169
    {
170
        return $this->renderTemplate(
171
            'SurfnetStepupRaRaBundle:Saml/Exception:badCredentials.html.twig',
172
            ['exception' => $exception]
173
        );
174
    }
175
176
    private function renderAuthenticationExceptionResponse(AuthenticationException $exception)
177
    {
178
        return $this->renderTemplate(
179
            'SurfnetStepupRaRaBundle:Saml/Exception:authenticationException.html.twig',
180
            ['exception' => $exception]
181
        );
182
    }
183
184
    /**
185
     * @param       $template
186
     * @param array $context
187
     * @return Response
188
     */
189
    private function renderTemplate($template, array $context)
190
    {
191
        /** @var Twig $twig */
192
        $twig = $this->container->get('twig');
193
        $html = $twig->render($template, $context);
194
195
        return new Response($html, Response::HTTP_UNAUTHORIZED);
196
    }
197
198
    /**
199
     * @return \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage
200
     */
201
    private function getTokenStorage()
202
    {
203
        return $this->container->get('security.token_storage');
204
    }
205
}
206