Completed
Push — master ( 7f018f...75cfb8 )
by A.
07:23 queued 03:02
created

ProcessSamlAuthenticationHandler   B

Complexity

Total Complexity 11

Size/Duplication

Total Lines 152
Duplicated Lines 12.5 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 11
c 2
b 0
f 1
lcom 1
cbo 18
dl 19
loc 152
rs 7.3333

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 19 19 1
C process() 0 79 9
A setNext() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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\StepupRa\RaBundle\Security\Authentication\Handler;
20
21
use Exception;
22
use SAML2_Response_Exception_PreconditionNotMetException as PreconditionNotMetException;
23
use Surfnet\SamlBundle\Http\Exception\AuthnFailedSamlResponseException;
24
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
25
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
26
use Surfnet\StepupBundle\Service\LoaResolutionService;
27
use Surfnet\StepupRa\RaBundle\Security\Authentication\AuthenticatedSessionStateHandler;
28
use Surfnet\StepupRa\RaBundle\Security\Authentication\SamlAuthenticationStateHandler;
29
use Surfnet\StepupRa\RaBundle\Security\Authentication\SamlInteractionProvider;
30
use Surfnet\StepupRa\RaBundle\Security\Authentication\Token\SamlToken;
31
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
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\AuthenticationManagerInterface;
36
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
37
use Symfony\Component\Security\Core\Exception\AuthenticationException;
38
39
/**
40
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) SamlResponse parsing, validation authentication and error handling
41
 *                                                 requires quite a few classes as it is fairly complex.
42
 */
43
class ProcessSamlAuthenticationHandler implements AuthenticationHandler
44
{
45
    /**
46
     * @var AuthenticationHandler
47
     */
48
    private $nextHandler;
49
50
    /**
51
     * @var TokenStorageInterface
52
     */
53
    private $tokenStorage;
54
55
    /**
56
     * @var SamlInteractionProvider
57
     */
58
    private $samlInteractionProvider;
59
60
    /**
61
     * @var SamlAuthenticationStateHandler
62
     */
63
    private $authenticationStateHandler;
64
65
    /**
66
     * @var AuthenticatedSessionStateHandler
67
     */
68
    private $authenticatedSession;
69
70
    /**
71
     * @var AuthenticationManagerInterface
72
     */
73
    private $authenticationManager;
74
75
    /**
76
     * @var LoaResolutionService
77
     */
78
    private $loaResolutionService;
79
80
    /**
81
     * @var SamlAuthenticationLogger
82
     */
83
    private $authenticationLogger;
84
85
    /**
86
     * @var EngineInterface
87
     */
88
    private $templating;
89
90 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
91
        TokenStorageInterface $tokenStorage,
92
        SamlInteractionProvider $samlInteractionProvider,
93
        SamlAuthenticationStateHandler $authenticationStateHandler,
94
        AuthenticatedSessionStateHandler $authenticatedSession,
95
        AuthenticationManagerInterface $authenticationManager,
96
        LoaResolutionService $loaResolutionService,
97
        SamlAuthenticationLogger $authenticationLogger,
98
        EngineInterface $templating
99
    ) {
100
        $this->tokenStorage               = $tokenStorage;
101
        $this->samlInteractionProvider    = $samlInteractionProvider;
102
        $this->authenticationStateHandler = $authenticationStateHandler;
103
        $this->authenticatedSession       = $authenticatedSession;
104
        $this->authenticationManager      = $authenticationManager;
105
        $this->loaResolutionService       = $loaResolutionService;
106
        $this->authenticationLogger       = $authenticationLogger;
107
        $this->templating                 = $templating;
108
    }
109
110
    public function process(GetResponseEvent $event)
111
    {
112
        if ($this->tokenStorage->getToken() === null
113
            && $this->samlInteractionProvider->isSamlAuthenticationInitiated()
114
        ) {
115
            $expectedInResponseTo = $this->authenticationStateHandler->getRequestId();
116
            $logger               = $this->authenticationLogger->forAuthentication($expectedInResponseTo);
117
118
            $logger->notice('No authenticated user and AuthnRequest pending, attempting to process SamlResponse');
119
120
            try {
121
                $assertion = $this->samlInteractionProvider->processSamlResponse($event->getRequest());
122
            } catch (AuthnFailedSamlResponseException $exception) {
123
                $logger->notice(sprintf('SAML Authentication failed at IdP: "%s"', $exception->getMessage()));
124
                $responseBody = $this->templating->render(
125
                    'SurfnetStepupRaRaBundle:Saml/Exception:authnFailed.html.twig',
126
                    ['exception' => $exception]
127
                );
128
129
                $event->setResponse(new Response($responseBody, Response::HTTP_UNAUTHORIZED));
130
131
                return;
132
            } catch (PreconditionNotMetException $exception) {
133
                $logger->notice(sprintf('SAMLResponse precondition not met: "%s"', $exception->getMessage()));
134
                $responseBody = $this->templating->render(
135
                    'SurfnetStepupRaRaBundle:Saml/Exception:preconditionNotMet.html.twig',
136
                    ['exception' => $exception]
137
                );
138
139
                $event->setResponse(new Response($responseBody, Response::HTTP_UNAUTHORIZED));
140
141
                return;
142
            } catch (Exception $exception) {
143
                $logger->error(sprintf('Failed SAMLResponse Parsing: "%s"', $exception->getMessage()));
144
145
                throw new AuthenticationException('Failed SAMLResponse parsing', 0, $exception);
146
            }
147
148
            if (!InResponseTo::assertEquals($assertion, $expectedInResponseTo)) {
149
                $logger->error('Unknown or unexpected InResponseTo in SAMLResponse');
150
151
                throw new AuthenticationException('Unknown or unexpected InResponseTo in SAMLResponse');
152
            }
153
154
            $logger->notice('Successfully processed SAMLResponse, attempting to authenticate');
155
156
            $loa = $this->loaResolutionService->getLoa($assertion->getAuthnContextClassRef());
157
158
            $token            = new SamlToken($loa);
0 ignored issues
show
Bug introduced by
It seems like $loa defined by $this->loaResolutionServ...AuthnContextClassRef()) on line 156 can be null; however, Surfnet\StepupRa\RaBundl...amlToken::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
159
            $token->assertion = $assertion;
160
161
            try {
162
                $authToken = $this->authenticationManager->authenticate($token);
163
            } catch (AuthenticationException $failed) {
164
                $logger->error(sprintf('Authentication Failed, reason: "%s"', $failed->getMessage()));
165
166
                // By default deny authorization
167
                $event->setResponse(new Response('', Response::HTTP_FORBIDDEN));
168
169
                return;
170
            }
171
172
            $this->authenticatedSession->logAuthenticationMoment();
173
            $this->tokenStorage->setToken($authToken);
174
175
            // migrate the session to prevent session hijacking
176
            $this->authenticatedSession->migrate();
177
178
            $event->setResponse(new RedirectResponse($this->authenticatedSession->getCurrentRequestUri()));
179
180
            $logger->notice('Authentication succeeded, redirecting to original location');
181
182
            return;
183
        }
184
185
        if ($this->nextHandler) {
186
            $this->nextHandler->process($event);
187
        }
188
    }
189
190
    public function setNext(AuthenticationHandler $handler)
191
    {
192
        $this->nextHandler = $handler;
193
    }
194
}
195