Completed
Push — feature/run-selenium-tests-poc ( 832342...a806da )
by Michiel
02:26
created

ServiceProviderContext::iStartAnAuthentication()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21

Duplication

Lines 21
Ratio 100 %

Importance

Changes 0
Metric Value
dl 21
loc 21
rs 9.584
c 0
b 0
f 0
cc 1
nc 1
nop 1
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 $currentIdP;
54
55
    /**
56
     * @var FixtureService
57
     */
58
    private $fixtureService;
59
60
    /**
61
     * @var KernelInterface
62
     */
63
    private $kernel;
64
65
    /**
66
     * @var MinkContext
67
     */
68
    private $minkContext;
69
70
    public function __construct(FixtureService $fixtureService)
71
    {
72
        $this->fixtureService = $fixtureService;
73
    }
74
75
    public function setKernel(KernelInterface $kernel)
76
    {
77
        $this->kernel = $kernel;
78
    }
79
80
    /**
81
     * @BeforeScenario
82
     */
83
    public function gatherContexts(BeforeScenarioScope $scope)
84
    {
85
        $environment = $scope->getEnvironment();
86
        $this->minkContext = $environment->getContext(MinkContext::class);
87
    }
88
89
    /**
90
     * @Given /^an SFO enabled SP with EntityID ([^\']*)$/
91
     */
92
    public function anSFOEnabledSPWithEntityID($entityId)
93
    {
94
        $this->registerSp($entityId, true);
95
    }
96
97
    /**
98
     * @Given /^an SP with EntityID ([^\']*)$/
99
     */
100
    public function anSPWithEntityID($entityId)
101
    {
102
        $this->registerSp($entityId, false);
103
    }
104
    /**
105
     * @Given /^an IdP with EntityID ([^\']*)$/
106
     */
107
    public function anIdPWithEntityID($entityId)
108
    {
109
        $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...
110
    }
111
112 View Code Duplication
    private function registerSp($entityId, $sfoEnabled)
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...
113
    {
114
        $publicKeyLoader = new KeyLoader();
115
        // todo: use from services_test.yml
116
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/sp.crt');
117
        $keys = $publicKeyLoader->getKeys();
118
        /** @var Key $cert */
119
        $cert = $keys->first();
120
121
        $spEntity = $this->fixtureService->registerSP($entityId, $cert['X509Certificate'], $sfoEnabled);
122
123
        $spEntity['configuration'] = json_decode($spEntity['configuration'], true);
124
        $this->currentSp = $spEntity;
125
    }
126
127 View Code Duplication
    private function registerIdP($entityId)
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...
128
    {
129
        $publicKeyLoader = new KeyLoader();
130
        // todo: use from services_test.yml
131
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/idp.crt');
132
        $keys = $publicKeyLoader->getKeys();
133
        /** @var Key $cert */
134
        $cert = $keys->first();
135
136
        $idpEntity = $this->fixtureService->registerIdP($entityId, $cert['X509Certificate']);
137
138
        $idpEntity['configuration'] = json_decode($idpEntity['configuration'], true);
139
        $this->currentIdP = $idpEntity;
140
    }
141
142
    /**
143
     * @When /^([^\']*) starts a SFO authentication$/
144
     */
145 View Code Duplication
    public function iStartASecondFactorAuthenticationOnTheSecondFactorOnlyEndpoint($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...
146
    {
147
        $authnRequest = new AuthnRequest();
148
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
149
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
150
        $authnRequest->setIssuer($this->currentSp['entityId']);
151
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
152
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
153
        $authnRequest->setNameId($this->buildNameId($nameId));
154
        // Sign with random key, does not mather for now.
155
        // todo: use from services_test.yml
156
        $authnRequest->setSignatureKey(
157
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
158
        );
159
        $authnRequest->setRequestedAuthnContext(
160
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/sfo-level2']]
161
        );
162
        $request = Saml2AuthnRequest::createNew($authnRequest);
163
        $query = $request->buildRequestQuery();
164
165
        $this->getSession()->visit($request->getDestination().'?'.$query);
166
    }
167
168
    /**
169
     * @When /^([^\']*) starts an SFO authentication$/
170
     */
171 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...
172
    {
173
        $authnRequest = new AuthnRequest();
174
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
175
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
176
        $authnRequest->setIssuer($this->currentSp['entityId']);
177
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
178
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
179
        $authnRequest->setNameId($this->buildNameId($nameId));
180
        // Sign with random key, does not mather for now.
181
        // todo: use from services_test.yml
182
        $authnRequest->setSignatureKey(
183
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
184
        );
185
        $authnRequest->setRequestedAuthnContext(
186
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/sfo-level2']]
187
        );
188
        $request = Saml2AuthnRequest::createNew($authnRequest);
189
        $query = $request->buildRequestQuery();
190
191
        $this->getSession()->visit($request->getDestination().'?'.$query);
192
    }
193
194
    /**
195
     * @When /^([^\']*) starts an authentication$/
196
     */
197 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...
198
    {
199
        $authnRequest = new AuthnRequest();
200
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
201
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
202
        $authnRequest->setIssuer($this->currentSp['entityId']);
203
        $authnRequest->setDestination(self::SSO_ENDPOINT_URL);
204
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
205
        $authnRequest->setNameId($this->buildNameId($nameId));
206
        // Sign with random key, does not mather for now.
207
        // todo: use from services_test.yml
208
        $authnRequest->setSignatureKey(
209
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
210
        );
211
        $authnRequest->setRequestedAuthnContext(
212
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/level2']]
213
        );
214
        $request = Saml2AuthnRequest::createNew($authnRequest);
215
        $query = $request->buildRequestQuery();
216
        $this->getSession()->visit($request->getDestination().'?'.$query);
217
    }
218
219
    /**
220
     * @When /^I authenticate at the IdP$/
221
     */
222
    public function iAuthenticateAtTheIdp()
223
    {
224
        $this->minkContext->pressButton('Submit');
225
    }
226
227
    /**
228
     * @return IdentityProvider
229
     */
230
    public function getIdentityProvider()
231
    {
232
        /** @var RequestStack $stack */
233
234
        $stack = $this->kernel->getContainer()->get('request_stack');
235
        $stack->push(Request::create('https://gateway.stepup.example.com'));
236
        $ip = $this->kernel->getContainer()->get('surfnet_saml.hosted.identity_provider');
237
        $stack->pop();
238
239
        return $ip;
240
    }
241
242 View Code Duplication
    private static function loadPrivateKey(PrivateKey $key)
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...
243
    {
244
        $keyLoader = new PrivateKeyLoader();
245
        $privateKey = $keyLoader->loadPrivateKey($key);
246
247
        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
248
        $key->loadKey($privateKey->getKeyAsString());
249
250
        return $key;
251
    }
252
253
    private function getSession()
254
    {
255
        return $this->minkContext->getSession();
256
    }
257
258
    /**
259
     * @param string $nameId
260
     * @return NameID
261
     */
262
    private function buildNameId($nameId)
263
    {
264
        $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...
265
        return $nameId;
266
    }
267
}