Completed
Pull Request — feature/acceptance-tests (#192)
by Michiel
02:05
created

anSFOEnabledSPWithEntityID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
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\Mink\Driver\Selenium2Driver;
24
use Behat\Symfony2Extension\Context\KernelAwareContext;
25
use RobRichards\XMLSecLibs\XMLSecurityKey;
26
use SAML2\AuthnRequest;
27
use SAML2\Certificate\Key;
28
use SAML2\Certificate\KeyLoader;
29
use SAML2\Certificate\PrivateKeyLoader;
30
use SAML2\Configuration\PrivateKey;
31
use SAML2\Constants;
32
use SAML2\XML\saml\NameID;
33
use Surfnet\SamlBundle\Entity\IdentityProvider;
34
use Surfnet\SamlBundle\SAML2\AuthnRequest as Saml2AuthnRequest;
35
use Surfnet\StepupGateway\Behat\Repository\SamlEntityRepository;
36
use Surfnet\StepupGateway\Behat\Service\FixtureService;
37
use Symfony\Component\HttpFoundation\Request;
38
use Symfony\Component\HttpFoundation\RequestStack;
39
use Symfony\Component\HttpKernel\KernelInterface;
40
41
class ServiceProviderContext implements Context, KernelAwareContext
42
{
43
    const SFO_ENDPOINT_URL = 'https://gateway.stepup.example.com/second-factor-only/single-sign-on';
44
    const SSO_ENDPOINT_URL = 'https://gateway.stepup.example.com/authentication/single-sign-on';
45
46
    /**
47
     * @var array
48
     */
49
    private $currentSp;
50
51
    /**
52
     * @var array
53
     */
54
    private $currentSfoSp;
55
56
    /**
57
     * @var array
58
     */
59
    private $currentIdP;
60
61
    /**
62
     * @var FixtureService
63
     */
64
    private $fixtureService;
65
66
    /**
67
     * @var KernelInterface
68
     */
69
    private $kernel;
70
71
    /**
72
     * @var MinkContext
73
     */
74
    private $minkContext;
75
76
    public function __construct(FixtureService $fixtureService)
77
    {
78
        $this->fixtureService = $fixtureService;
79
    }
80
81
    public function setKernel(KernelInterface $kernel)
82
    {
83
        $this->kernel = $kernel;
84
    }
85
86
    /**
87
     * @BeforeScenario
88
     */
89
    public function gatherContexts(BeforeScenarioScope $scope)
90
    {
91
        $environment = $scope->getEnvironment();
92
        $this->minkContext = $environment->getContext(MinkContext::class);
93
    }
94
95
    /**
96
     * @Given /^an SFO enabled SP with EntityID ([^\']*)$/
97
     */
98
    public function anSFOEnabledSPWithEntityID($entityId)
99
    {
100
        $this->registerSp($entityId, true);
101
    }
102
103
    /**
104
     * @Given /^an SP with EntityID ([^\']*)$/
105
     */
106
    public function anSPWithEntityID($entityId)
107
    {
108
        $this->registerSp($entityId, false);
109
    }
110
    /**
111
     * @Given /^an IdP with EntityID ([^\']*)$/
112
     */
113
    public function anIdPWithEntityID($entityId)
114
    {
115
        $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...
116
    }
117
118
    private function registerSp($entityId, $sfoEnabled)
119
    {
120
        $publicKeyLoader = new KeyLoader();
121
        // todo: use from services_test.yml
122
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/sp.crt');
123
        $keys = $publicKeyLoader->getKeys();
124
        /** @var Key $cert */
125
        $cert = $keys->first();
126
127
        $spEntity = $this->fixtureService->registerSP($entityId, $cert['X509Certificate'], $sfoEnabled);
128
129
        $spEntity['configuration'] = json_decode($spEntity['configuration'], true);
130
        if ($sfoEnabled) {
131
            $this->currentSfoSp = $spEntity;
132
        } else {
133
            $this->currentSp = $spEntity;
134
        }
135
    }
136
137
    private function registerIdP($entityId)
138
    {
139
        $publicKeyLoader = new KeyLoader();
140
        // todo: use from services_test.yml
141
        $publicKeyLoader->loadCertificateFile('/var/www/ci/certificates/idp.crt');
142
        $keys = $publicKeyLoader->getKeys();
143
        /** @var Key $cert */
144
        $cert = $keys->first();
145
146
        $idpEntity = $this->fixtureService->registerIdP($entityId, $cert['X509Certificate']);
147
148
        $idpEntity['configuration'] = json_decode($idpEntity['configuration'], true);
149
        $this->currentIdP = $idpEntity;
150
    }
151
152
    /**
153
     * @When /^([^\']*) starts an SFO authentication$/
154
     */
155 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...
156
    {
157
        $authnRequest = new AuthnRequest();
158
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
159
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
160
        $authnRequest->setIssuer($this->currentSfoSp['entityId']);
161
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
162
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
163
        $authnRequest->setNameId($this->buildNameId($nameId));
164
        // Sign with random key, does not mather for now.
165
        // todo: use from services_test.yml
166
        $authnRequest->setSignatureKey(
167
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
168
        );
169
        $authnRequest->setRequestedAuthnContext(
170
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/sfo-level2']]
171
        );
172
        $request = Saml2AuthnRequest::createNew($authnRequest);
173
        $query = $request->buildRequestQuery();
174
175
        $this->getSession()->visit($request->getDestination().'?'.$query);
176
    }
177
178
    /**
179
     * @When /^([^\']*) starts an SFO authentication requiring ([^\']*)$/
180
     */
181 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...
182
    {
183
        $authnRequest = new AuthnRequest();
184
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
185
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
186
        $authnRequest->setIssuer($this->currentSfoSp['entityId']);
187
        $authnRequest->setDestination(self::SFO_ENDPOINT_URL);
188
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
189
        $authnRequest->setNameId($this->buildNameId($nameId));
190
        // Sign with random key, does not mather for now.
191
        // todo: use from services_test.yml
192
        $authnRequest->setSignatureKey(
193
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
194
        );
195
        $authnRequest->setRequestedAuthnContext(
196
            ['AuthnContextClassRef' => [$loa]]
197
        );
198
        $request = Saml2AuthnRequest::createNew($authnRequest);
199
        $query = $request->buildRequestQuery();
200
201
        $this->getSession()->visit($request->getDestination().'?'.$query);
202
    }
203
204
    /**
205
     * @When /^([^\']*) starts an authentication$/
206
     */
207 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...
208
    {
209
        $authnRequest = new AuthnRequest();
210
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
211
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
212
        $authnRequest->setIssuer($this->currentSp['entityId']);
213
        $authnRequest->setDestination(self::SSO_ENDPOINT_URL);
214
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
215
        $authnRequest->setNameId($this->buildNameId($nameId));
216
        // Sign with random key, does not mather for now.
217
        // todo: use from services_test.yml
218
        $authnRequest->setSignatureKey(
219
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
220
        );
221
        $authnRequest->setRequestedAuthnContext(
222
            ['AuthnContextClassRef' => ['http://stepup.example.com/assurance/level2']]
223
        );
224
        $request = Saml2AuthnRequest::createNew($authnRequest);
225
        $query = $request->buildRequestQuery();
226
        $this->getSession()->visit($request->getDestination().'?'.$query);
227
    }
228
229
    /**
230
     * @When /^([^\']*) starts an authentication requiring ([^\']*)$/
231
     */
232 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...
233
    {
234
        $authnRequest = new AuthnRequest();
235
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
236
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
237
        $authnRequest->setIssuer($this->currentSp['entityId']);
238
        $authnRequest->setDestination(self::SSO_ENDPOINT_URL);
239
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
240
        $authnRequest->setNameId($this->buildNameId($nameId));
241
        // Sign with random key, does not mather for now.
242
        // todo: use from services_test.yml
243
        $authnRequest->setSignatureKey(
244
            $this->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.pem', 'default'))
245
        );
246
        $authnRequest->setRequestedAuthnContext(
247
            ['AuthnContextClassRef' => [$loa]]
248
        );
249
        $request = Saml2AuthnRequest::createNew($authnRequest);
250
        $query = $request->buildRequestQuery();
251
        $this->getSession()->visit($request->getDestination().'?'.$query);
252
    }
253
254
    /**
255
     * @When /^I authenticate at the IdP as ([^\']*)$/
256
     */
257
    public function iAuthenticateAtTheIdp($username)
258
    {
259
        $this->minkContext->fillField('form_username', $username);
260
        // Submit the IdP 'authentication' form
261
        $this->minkContext->pressButton('Submit');
262
        if (!$this->minkContext->isSelenium()) {
263
            // Submit the SAML Response
264
            $this->minkContext->pressButton('Submit');
265
        }
266
    }
267
268
    /**
269
     * @return IdentityProvider
270
     */
271
    public function getIdentityProvider()
272
    {
273
        /** @var RequestStack $stack */
274
275
        $stack = $this->kernel->getContainer()->get('request_stack');
276
        $stack->push(Request::create('https://gateway.stepup.example.com'));
277
        $ip = $this->kernel->getContainer()->get('surfnet_saml.hosted.identity_provider');
278
        $stack->pop();
279
280
        return $ip;
281
    }
282
283
    private static function loadPrivateKey(PrivateKey $key)
284
    {
285
        $keyLoader = new PrivateKeyLoader();
286
        $privateKey = $keyLoader->loadPrivateKey($key);
287
288
        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
289
        $key->loadKey($privateKey->getKeyAsString());
290
291
        return $key;
292
    }
293
294
    private function getSession()
295
    {
296
        return $this->minkContext->getSession();
297
    }
298
299
    /**
300
     * @param string $nameId
301
     * @return NameID
302
     */
303
    private function buildNameId($nameId)
304
    {
305
        $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...
306
        return $nameId;
307
    }
308
}