SamlListener::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * Copyright 2015 SURFnet B.V.
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 OpenConext\ProfileBundle\Security\Firewall;
20
21
use Exception;
22
use OpenConext\ProfileBundle\Saml\StateHandler;
23
use OpenConext\ProfileBundle\Security\Authentication\SamlInteractionProvider;
24
use OpenConext\ProfileBundle\Security\Authentication\Token\SamlToken;
25
use SAML2\Response\Exception\PreconditionNotMetException;
26
use Surfnet\SamlBundle\Http\Exception\AuthnFailedSamlResponseException;
27
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
28
use Symfony\Component\HttpFoundation\RedirectResponse;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\HttpFoundation\Response;
31
use Symfony\Component\HttpFoundation\Session\SessionInterface;
32
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
33
use \Psr\Log\LoggerInterface;
34
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
35
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
36
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
37
use Symfony\Component\Security\Core\Exception\AuthenticationException;
38
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
39
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
40
use Twig_Environment as Twig;
41
42
/**
43
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
44
 */
45
class SamlListener implements ListenerInterface
46
{
47
    /**
48
     * @var SessionInterface
49
     */
50
    private $session;
51
52
    /**
53
     * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
54
     */
55
    private $urlMatcher;
56
57
    /**
58
     * @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface
59
     */
60
    private $tokenStorage;
61
62
    /**
63
     * @var \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface
64
     */
65
    private $authenticationManager;
66
67
    /**
68
     * @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger
69
     */
70
    private $logger;
71
72
    /**
73
     * @var \OpenConext\ProfileBundle\Security\Authentication\SamlInteractionProvider
74
     */
75
    private $samlInteractionProvider;
76
77
    /**
78
     * @var \OpenConext\ProfileBundle\Saml\StateHandler
79
     */
80
    private $stateHandler;
81
82
    /**
83
     * @var \Twig_Environment
84
     */
85
    private $twig;
86
87
    public function __construct(
88
        SessionInterface $session,
89
        UrlMatcherInterface $urlMatcher,
90
        TokenStorageInterface $tokenStorage,
91
        AuthenticationManagerInterface $authenticationManager,
92
        SamlInteractionProvider $samlInteractionProvider,
93
        StateHandler $stateHandler,
94
        LoggerInterface $logger,
95
        Twig $twig
96
    ) {
97
        $this->session                  = $session;
98
        $this->urlMatcher               = $urlMatcher;
99
        $this->tokenStorage             = $tokenStorage;
100
        $this->authenticationManager    = $authenticationManager;
101
        $this->samlInteractionProvider  = $samlInteractionProvider;
102
        $this->stateHandler             = $stateHandler;
103
        $this->logger                   = $logger;
0 ignored issues
show
Documentation Bug introduced by
$logger is of type object<Psr\Log\LoggerInterface>, but the property $logger was declared to be of type object<Surfnet\SamlBundl...mlAuthenticationLogger>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
104
        $this->twig = $twig;
105
    }
106
107
    public function handle(GetResponseEvent $event)
108
    {
109
        try {
110
            $this->handleEvent($event);
111
        } catch (\Exception $e) {
112
            $this->samlInteractionProvider->reset();
113
            throw $e;
114
        }
115
    }
116
117
    /**
118
     * @param GetResponseEvent $event
119
     */
120
    private function handleEvent(GetResponseEvent $event)
121
    {
122
        if ($this->tokenStorage->getToken()) {
123
            return;
124
        }
125
126
        if (!$this->isAcsRequest($event->getRequest()) ||
127
            !$this->samlInteractionProvider->isSamlAuthenticationInitiated()) {
128
            $this->sendAuthnRequest($event);
129
130
            return;
131
        }
132
133
        $expectedInResponseTo = $this->stateHandler->getRequestId();
134
        $logger = $this->logger;
135
136
        try {
137
            $assertion = $this->samlInteractionProvider->processSamlResponse($event->getRequest());
138
        } catch (PreconditionNotMetException $e) {
139
            $logger->notice(sprintf('SAML response precondition not met: "%s"', $e->getMessage()));
140
            $this->setPreconditionExceptionResponse($e, $event);
141
142
            return;
143
        } catch (Exception $e) {
144
            $logger->error(sprintf('Failed SAMLResponse Parsing: "%s"', $e->getMessage()));
145
            throw new AuthenticationException('Failed SAMLResponse parsing', 0, $e);
146
        }
147
148
        if (!InResponseTo::assertEquals($assertion, $expectedInResponseTo)) {
149
            $logger->error('Unknown or unexpected InResponseTo in SAMLResponse');
150
            throw new AuthenticationException('Unknown or unexpected InResponseTo in SAMLResponse');
151
        }
152
153
        $logger->notice('Successfully processed SAMLResponse, attempting to authenticate');
154
155
        $token = new SamlToken();
156
        $token->assertion = $assertion;
157
158
        try {
159
            $authToken = $this->authenticationManager->authenticate($token);
160
        } catch (AuthenticationException $failed) {
161
            $logger->error(sprintf('Authentication Failed, reason: "%s"', $failed->getMessage()));
162
            $this->setAuthenticationFailedResponse($event);
163
164
            return;
165
        }
166
167
        $this->tokenStorage->setToken($authToken);
168
169
        // migrate the session to prevent session hijacking
170
        $this->session->migrate();
171
172
        $event->setResponse(new RedirectResponse($this->stateHandler->getCurrentRequestUri()));
173
        $logger->notice('Authentication succeeded, redirecting to original location');
174
    }
175
176
    /**
177
     * Check if this is a request to the ACS location.
178
     *
179
     * @return bool
180
     */
181
    private function isAcsRequest(Request $request)
182
    {
183
        try {
184
            $params = $this->urlMatcher->match($request->getPathInfo());
185
        } catch (ResourceNotFoundException $e) {
186
            return false;
187
        }
188
189
        return $params['_route'] === 'profile.saml_consume_assertion';
190
    }
191
192
    /**
193
     * @param GetResponseEvent $event
194
     */
195
    private function sendAuthnRequest(GetResponseEvent $event)
196
    {
197
        $this->stateHandler->setCurrentRequestUri($event->getRequest()->getUri());
198
199
        $event->setResponse($this->samlInteractionProvider->initiateSamlRequest());
200
201
        $logger = $this->logger;
202
        $logger->notice('Sending AuthnRequest');
203
    }
204
205
    /**
206
     * @param PreconditionNotMetException $exception
207
     * @param GetResponseEvent $event
208
     */
209
    private function setPreconditionExceptionResponse(PreconditionNotMetException $exception, GetResponseEvent $event)
210
    {
211
        $template = null;
0 ignored issues
show
Unused Code introduced by
$template is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
212
213
        if ($exception instanceof AuthnFailedSamlResponseException) {
214
            $template = 'SurfnetStepupSelfServiceSelfServiceBundle:Saml/Exception:authnFailed.html.twig';
215
        } else {
216
            $template = 'SurfnetStepupSelfServiceSelfServiceBundle:Saml/Exception:preconditionNotMet.html.twig';
217
        }
218
219
        $html = $this->twig->render($template, ['exception' => $exception]);
220
        $event->setResponse(new Response($html, Response::HTTP_UNAUTHORIZED));
221
    }
222
223
    /**
224
     * Deny authentication by default
225
     * @param GetResponseEvent $event
226
     */
227
    private function setAuthenticationFailedResponse(GetResponseEvent $event)
228
    {
229
        $response = new Response();
230
        $response->setStatusCode(Response::HTTP_FORBIDDEN);
231
        $event->setResponse($response);
232
    }
233
}
234