Completed
Push — feature/enable-behat-tests ( 5b7c06...1d2dab )
by Michiel
01:47
created

ServiceProviderContext   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 266
Duplicated Lines 32.33 %

Coupling/Cohesion

Components 2
Dependencies 16

Importance

Changes 0
Metric Value
wmc 18
lcom 2
cbo 16
dl 86
loc 266
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setKernel() 0 4 1
A gatherContexts() 0 5 1
A anSFOEnabledSPWithEntityID() 0 4 1
A anSPWithEntityID() 0 4 1
A anIdPWithEntityID() 0 4 1
A registerSp() 0 18 2
A registerIdP() 0 14 1
A iStartAnSFOAuthentication() 22 22 1
A iStartAnSFOAuthenticationWithLoaRequirement() 22 22 1
A iAuthenticateAtTheIdp() 0 8 1
A getIdentityProvider() 0 11 1
A loadPrivateKey() 0 10 1
A getSession() 0 4 1
A buildNameId() 0 5 1
A iStartAnAuthentication() 21 21 1
A iStartAnSsoAuthenticationWithLoaRequirement() 21 21 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 2020 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 Surfnet\StepupGateway\Behat;
20
21
use Behat\Behat\Context\Context;
22
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
23
use Behat\Symfony2Extension\Context\KernelAwareContext;
24
use RobRichards\XMLSecLibs\XMLSecurityKey;
25
use SAML2\AuthnRequest;
26
use SAML2\Certificate\Key;
27
use SAML2\Certificate\KeyLoader;
28
use SAML2\Certificate\PrivateKeyLoader;
29
use SAML2\Configuration\PrivateKey;
30
use SAML2\Constants;
31
use SAML2\XML\saml\NameID;
32
use Surfnet\SamlBundle\Entity\IdentityProvider;
33
use Surfnet\SamlBundle\SAML2\AuthnRequest as Saml2AuthnRequest;
34
use Surfnet\StepupGateway\Behat\Repository\SamlEntityRepository;
35
use Surfnet\StepupGateway\Behat\Service\FixtureService;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\HttpFoundation\RequestStack;
38
use Symfony\Component\HttpKernel\KernelInterface;
39
40
class ServiceProviderContext implements Context, KernelAwareContext
41
{
42
    const SFO_ENDPOINT_URL = 'https://gateway.stepup.example.com/second-factor-only/single-sign-on';
43
    const SSO_ENDPOINT_URL = 'https://gateway.stepup.example.com/authentication/single-sign-on';
44
45
    /**
46
     * @var array
47
     */
48
    private $currentSp;
49
50
    /**
51
     * @var array
52
     */
53
    private $currentSfoSp;
54
55
    /**
56
     * @var array
57
     */
58
    private $currentIdP;
59
60
    /**
61
     * @var FixtureService
62
     */
63
    private $fixtureService;
64
65
    /**
66
     * @var KernelInterface
67
     */
68
    private $kernel;
69
70
    /**
71
     * @var MinkContext
72
     */
73
    private $minkContext;
74
75
    public function __construct(FixtureService $fixtureService)
76
    {
77
        $this->fixtureService = $fixtureService;
78
    }
79
80
    public function setKernel(KernelInterface $kernel)
81
    {
82
        $this->kernel = $kernel;
83
    }
84
85
    /**
86
     * @BeforeScenario
87
     */
88
    public function gatherContexts(BeforeScenarioScope $scope)
89
    {
90
        $environment = $scope->getEnvironment();
91
        $this->minkContext = $environment->getContext(MinkContext::class);
92
    }
93
94
    /**
95
     * @Given /^an SFO enabled SP with EntityID ([^\']*)$/
96
     */
97
    public function anSFOEnabledSPWithEntityID($entityId)
98
    {
99
        $this->registerSp($entityId, true);
100
    }
101
102
    /**
103
     * @Given /^an SP with EntityID ([^\']*)$/
104
     */
105
    public function anSPWithEntityID($entityId)
106
    {
107
        $this->registerSp($entityId, false);
108
    }
109
    /**
110
     * @Given /^an IdP with EntityID ([^\']*)$/
111
     */
112
    public function anIdPWithEntityID($entityId)
113
    {
114
        $this->registerIdp($entityId, false);
0 ignored issues
show
Unused Code introduced by
The call to ServiceProviderContext::registerIdP() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
115
    }
116
117
    private function registerSp($entityId, $sfoEnabled)
118
    {
119
        $publicKeyLoader = new KeyLoader();
120
        // todo: use from services_test.yml
121
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/sp.crt');
122
        $keys = $publicKeyLoader->getKeys();
123
        /** @var Key $cert */
124
        $cert = $keys->first();
125
126
        $spEntity = $this->fixtureService->registerSP($entityId, $cert['X509Certificate'], $sfoEnabled);
127
128
        $spEntity['configuration'] = json_decode($spEntity['configuration'], true);
129
        if ($sfoEnabled) {
130
            $this->currentSfoSp = $spEntity;
131
        } else {
132
            $this->currentSp = $spEntity;
133
        }
134
    }
135
136
    private function registerIdP($entityId)
137
    {
138
        $publicKeyLoader = new KeyLoader();
139
        // todo: use from services_test.yml
140
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/idp.crt');
141
        $keys = $publicKeyLoader->getKeys();
142
        /** @var Key $cert */
143
        $cert = $keys->first();
144
145
        $idpEntity = $this->fixtureService->registerIdP($entityId, $cert['X509Certificate']);
146
147
        $idpEntity['configuration'] = json_decode($idpEntity['configuration'], true);
148
        $this->currentIdP = $idpEntity;
149
    }
150
151
    /**
152
     * @When /^([^\']*) starts an SFO authentication$/
153
     */
154 View Code Duplication
    public function iStartAnSFOAuthentication($nameId)
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...
155
    {
156
        $authnRequest = new AuthnRequest();
157
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
158
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
159
        $authnRequest->setIssuer($this->currentSfoSp['entityId']);
160
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
161
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
162
        $authnRequest->setNameId($this->buildNameId($nameId));
163
        // Sign with random key, does not mather for now.
164
        // todo: use from services_test.yml
165
        $authnRequest->setSignatureKey(
166
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
167
        );
168
        $authnRequest->setRequestedAuthnContext(
169
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/sfo-level2']]
170
        );
171
        $request = Saml2AuthnRequest::createNew($authnRequest);
172
        $query = $request->buildRequestQuery();
173
174
        $this->getSession()->visit($request->getDestination().'?'.$query);
175
    }
176
177
    /**
178
     * @When /^([^\']*) starts an SFO authentication requiring ([^\']*)$/
179
     */
180 View Code Duplication
    public function iStartAnSFOAuthenticationWithLoaRequirement($nameId, $loa)
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...
181
    {
182
        $authnRequest = new AuthnRequest();
183
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
184
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
185
        $authnRequest->setIssuer($this->currentSfoSp['entityId']);
186
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
187
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
188
        $authnRequest->setNameId($this->buildNameId($nameId));
189
        // Sign with random key, does not mather for now.
190
        // todo: use from services_test.yml
191
        $authnRequest->setSignatureKey(
192
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
193
        );
194
        $authnRequest->setRequestedAuthnContext(
195
            ['AuthnContextClassRef' => [$loa]]
196
        );
197
        $request = Saml2AuthnRequest::createNew($authnRequest);
198
        $query = $request->buildRequestQuery();
199
200
        $this->getSession()->visit($request->getDestination().'?'.$query);
201
    }
202
203
    /**
204
     * @When /^([^\']*) starts an authentication$/
205
     */
206 View Code Duplication
    public function iStartAnAuthentication($nameId)
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...
207
    {
208
        $authnRequest = new AuthnRequest();
209
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
210
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
211
        $authnRequest->setIssuer($this->currentSp['entityId']);
212
        $authnRequest->setDestination(self::SSO_ENDPOINT_URL);
213
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
214
        $authnRequest->setNameId($this->buildNameId($nameId));
215
        // Sign with random key, does not mather for now.
216
        // todo: use from services_test.yml
217
        $authnRequest->setSignatureKey(
218
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
219
        );
220
        $authnRequest->setRequestedAuthnContext(
221
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/level2']]
222
        );
223
        $request = Saml2AuthnRequest::createNew($authnRequest);
224
        $query = $request->buildRequestQuery();
225
        $this->getSession()->visit($request->getDestination().'?'.$query);
226
    }
227
228
    /**
229
     * @When /^([^\']*) starts an authentication requiring ([^\']*)$/
230
     */
231 View Code Duplication
    public function iStartAnSsoAuthenticationWithLoaRequirement($nameId, $loa)
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...
232
    {
233
        $authnRequest = new AuthnRequest();
234
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
235
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
236
        $authnRequest->setIssuer($this->currentSp['entityId']);
237
        $authnRequest->setDestination(self::SSO_ENDPOINT_URL);
238
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
239
        $authnRequest->setNameId($this->buildNameId($nameId));
240
        // Sign with random key, does not mather for now.
241
        // todo: use from services_test.yml
242
        $authnRequest->setSignatureKey(
243
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
244
        );
245
        $authnRequest->setRequestedAuthnContext(
246
            ['AuthnContextClassRef' => [$loa]]
247
        );
248
        $request = Saml2AuthnRequest::createNew($authnRequest);
249
        $query = $request->buildRequestQuery();
250
        $this->getSession()->visit($request->getDestination().'?'.$query);
251
    }
252
253
    /**
254
     * @When /^I authenticate at the IdP as ([^\']*)$/
255
     */
256
    public function iAuthenticateAtTheIdp($username)
257
    {
258
        $this->minkContext->fillField('form_username', $username);
259
        // Submit the form
260
        $this->minkContext->pressButton('Submit');
261
        // Submit the SAML Response
262
        $this->minkContext->pressButton('Submit');
263
    }
264
265
    /**
266
     * @return IdentityProvider
267
     */
268
    public function getIdentityProvider()
269
    {
270
        /** @var RequestStack $stack */
271
272
        $stack = $this->kernel->getContainer()->get('request_stack');
273
        $stack->push(Request::create('https://gateway.stepup.example.com'));
274
        $ip = $this->kernel->getContainer()->get('surfnet_saml.hosted.identity_provider');
275
        $stack->pop();
276
277
        return $ip;
278
    }
279
280
    private static function loadPrivateKey(PrivateKey $key)
281
    {
282
        $keyLoader = new PrivateKeyLoader();
283
        $privateKey = $keyLoader->loadPrivateKey($key);
284
285
        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
286
        $key->loadKey($privateKey->getKeyAsString());
287
288
        return $key;
289
    }
290
291
    private function getSession()
292
    {
293
        return $this->minkContext->getSession();
294
    }
295
296
    /**
297
     * @param string $nameId
298
     * @return NameID
299
     */
300
    private function buildNameId($nameId)
301
    {
302
        $nameId = NameID::fromArray(['Value' => $nameId, 'Format' =>  Constants::NAMEFORMAT_UNSPECIFIED]);
0 ignored issues
show
Deprecated Code introduced by
The method SAML2\XML\saml\NameIDType::fromArray() has been deprecated.

This method has been deprecated.

Loading history...
303
        return $nameId;
304
    }
305
}