Passed
Push — feature/create_docker_publish_... ( f65c8d...09f4fb )
by Michiel
15:10 queued 08:47
created

FeatureContext::iPassThroughTheGateway()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\BeforeFeatureScope;
23
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
24
use Behat\Mink\Exception\ExpectationException;
25
use RuntimeException;
26
use Surfnet\StepupGateway\Behat\Service\FixtureService;
27
use function sprintf;
28
29
class FeatureContext implements Context
30
{
31
    /**
32
     * @var FixtureService
33
     */
34
    private $fixtureService;
35
36
    private $whitelistedInstitutions = [];
37
38
    /**
39
     * @var MinkContext
40
     */
41
    private $minkContext;
42
43
    /**
44
     * @var array
45
     */
46
    private $currentToken;
47
48
    private $sso2faCookieName;
49
50
    /**
51
     * @var string|null
52
     */
53
    private $previousSsoOn2faCookieValue;
54
55
    public function __construct(FixtureService $fixtureService)
56
    {
57
        $this->fixtureService = $fixtureService;
58
        $this->sso2faCookieName = 'stepup-gateway_sso-on-second-factor-authentication';
59
    }
60
61
    /**
62
     * @BeforeFeature
63
     */
64
    public static function setupDatabase(BeforeFeatureScope $scope)
0 ignored issues
show
Unused Code introduced by
The parameter $scope is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
    public static function setupDatabase(/** @scrutinizer ignore-unused */ BeforeFeatureScope $scope)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
65
    {
66
        // Generate test databases
67
        echo "Preparing test schemas\n";
68
        shell_exec("/var/www/bin/console doctrine:schema:drop --env=smoketest --force");
69
        shell_exec("/var/www/bin/console doctrine:schema:create --env=smoketest");
70
    }
71
72
    /**
73
     * @BeforeScenario
74
     */
75
    public function gatherContexts(BeforeScenarioScope $scope)
76
    {
77
        $environment = $scope->getEnvironment();
78
        $this->minkContext = $environment->getContext(MinkContext::class);
0 ignored issues
show
Bug introduced by
The method getContext() does not exist on Behat\Testwork\Environment\Environment. It seems like you code against a sub-type of Behat\Testwork\Environment\Environment such as Behat\Behat\Context\Envi...lizedContextEnvironment or Behat\Behat\Context\Envi...lizedContextEnvironment. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

78
        /** @scrutinizer ignore-call */ 
79
        $this->minkContext = $environment->getContext(MinkContext::class);
Loading history...
79
    }
80
81
    /**
82
     * @Given /^a user from "([^"]*)" identified by "([^"]*)" with a vetted "([^"]*)" token$/
83
     */
84
    public function aUserIdentifiedByWithAVettedToken($institution, $nameId, $tokenType)
85
    {
86
        switch (strtolower($tokenType)) {
87
            case "yubikey":
88
                $this->currentToken = $this->fixtureService->registerYubikeyToken($nameId, $institution);
89
                break;
90
            case "sms":
91
                $this->currentToken = $this->fixtureService->registerSmsToken($nameId, $institution);
92
                break;
93
            case "tiqr":
94
                $this->currentToken = $this->fixtureService->registerTiqrToken($nameId, $institution);
95
                break;
96
        }
97
    }
98
99
    /**
100
     * @Given /^a user from "([^"]*)" identified by "([^"]*)" with a self-asserted "([^"]*)" token$/
101
     */
102
    public function aUserIdentifiedByWithASelfAssertedToken($institution, $nameId, $tokenType)
103
    {
104
        switch (strtolower($tokenType)) {
105
            case "yubikey":
106
                $this->currentToken = $this->fixtureService->registerYubikeyToken($nameId, $institution, true);
107
                break;
108
            case "sms":
109
                $this->currentToken = $this->fixtureService->registerSmsToken($nameId, $institution, true);
110
                break;
111
            case "tiqr":
112
                $this->currentToken = $this->fixtureService->registerTiqrToken($nameId, $institution, true);
113
                break;
114
        }
115
    }
116
117
    /**
118
     * @Then I should see the Yubikey OTP screen
119
     */
120
    public function iShouldSeeTheYubikeyOtpScreen()
121
    {
122
        $this->minkContext->assertPageContainsText('Your YubiKey-code');
123
    }
124
125
    /**
126
     * @Then I should see the SMS verification screen
127
     */
128
    public function iShouldSeeTheSMSScreen()
129
    {
130
        $this->minkContext->assertPageContainsText('Enter the received SMS-code');
131
        $this->minkContext->assertPageContainsText('Send again');
132
    }
133
134
    /**
135
     * @Given /^I should see the Tiqr authentication screen$/
136
     */
137
    public function iShouldSeeTheTiqrAuthenticationScreen()
138
    {
139
        $this->minkContext->pressButton('Submit');
140
        $this->minkContext->assertPageContainsText('Log in with Tiqr');
141
    }
142
143
    /**
144
     * @When I enter the OTP
145
     */
146
    public function iEnterTheOtp()
147
    {
148
        $this->minkContext->fillField('gateway_verify_yubikey_otp_otp', 'bogus-otp-we-use-a-mock-yubikey-service');
149
        $this->minkContext->pressButton('gateway_verify_yubikey_otp_submit');
150
        $this->minkContext->pressButton('Submit');
151
    }
152
153
    /**
154
     * @When I enter the SMS verification code
155
     */
156
    public function iEnterTheSmsVerificationCode()
157
    {
158
        $cookieValue = $this->minkContext->getSession()->getDriver()->getCookie('smoketest-sms-service');
159
        $matches = [];
160
        preg_match('/^Your\ SMS\ code:\ (.*)$/', $cookieValue, $matches);
0 ignored issues
show
Bug introduced by
It seems like $cookieValue can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

160
        preg_match('/^Your\ SMS\ code:\ (.*)$/', /** @scrutinizer ignore-type */ $cookieValue, $matches);
Loading history...
161
        $this->minkContext->fillField('gateway_verify_sms_challenge_challenge', $matches[1]);
162
        $this->minkContext->pressButton('gateway_verify_sms_challenge_verify_challenge');
163
        $this->minkContext->pressButton('Submit');
164
    }
165
166
    /**
167
     * @When I enter the expired SMS verification code
168
     */
169
    public function iEnterTheExpiredSmsVerificationCode()
170
    {
171
        $cookieValue = $this->minkContext->getSession()->getDriver()->getCookie('smoketest-sms-service');
172
        $matches = [];
173
        preg_match('/^Your\ SMS\ code:\ (.*)$/', $cookieValue, $matches);
0 ignored issues
show
Bug introduced by
It seems like $cookieValue can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
        preg_match('/^Your\ SMS\ code:\ (.*)$/', /** @scrutinizer ignore-type */ $cookieValue, $matches);
Loading history...
174
        $this->minkContext->fillField('gateway_verify_sms_challenge_challenge', $matches[1]);
175
        $this->minkContext->pressButton('gateway_verify_sms_challenge_verify_challenge');
176
    }
177
178
    /**
179
     * @When I finish the Tiqr authentication
180
     */
181
    public function iFinishGsspAuthentication()
182
    {
183
        $this->minkContext->pressButton('Submit');
184
        $this->minkContext->pressButton('Submit');
185
    }
186
187
    /**
188
     * @Given /^a whitelisted institution ([^"]*)$/
189
     */
190
    public function aWhitelistedInstitution($institution)
191
    {
192
        $this->whitelistedInstitutions[] = $this->fixtureService->whitelist($institution)['institution'];
193
    }
194
195
    /**
196
     * @Given /^an institution "([^"]*)" that allows "([^"]*)"$/
197
     */
198
    public function anInstitutionThatAllows(string $institution, string $option)
199
    {
200
        switch(true) {
201
            case $option === 'sso_on_2fa':
202
                $optionColumnName = 'sso_on2fa_enabled';
203
                break;
204
            default:
205
                throw new RuntimeException(sprintf('Option "%s" is not supported', $option));
206
        }
207
        $this->fixtureService->configureBoolean($institution, $optionColumnName, true);
208
    }
209
210
    /**
211
     * @Then /^I select my ([^"]*) token on the WAYG$/
212
     */
213
    public function iShouldSelectMyTokenOnTheWAYG($tokenType)
214
    {
215
        switch (strtolower($tokenType)) {
216
            case "yubikey":
217
                $this->minkContext->pressButton('gateway_choose_second_factor_choose_yubikey');
218
                break;
219
            case "sms":
220
                $this->minkContext->pressButton('gateway_choose_second_factor_choose_sms');
221
                break;
222
            case "tiqr":
223
                $this->minkContext->pressButton('gateway_choose_second_factor_choose_tiqr');
224
                break;
225
        }
226
    }
227
228
    /**
229
     * @Then /^I should be on the WAYG$/
230
     */
231
    public function iShouldBeOnTheWAYG()
232
    {
233
        $this->minkContext->assertPageContainsText('Choose a token for login');
234
    }
235
236
    /**
237
     * @Then /^an error response is posted back to the SP$/
238
     */
239
    public function anErrorResponseIsPostedBackToTheSP()
240
    {
241
        $this->minkContext->pressButton('Submit');
242
243
    }
244
245
    /**
246
     * @Given /^I cancel the authentication$/
247
     */
248
    public function iCancelTheAuthentication()
249
    {
250
        $this->minkContext->pressButton('Cancel');
251
    }
252
253
    /**
254
     * @Given /^I pass through the Gateway$/
255
     */
256
    public function iPassThroughTheGateway()
257
    {
258
        $this->minkContext->pressButton('Submit');
259
    }
260
261
    /**
262
     * @Given /^I pass through the IdP/
263
     */
264
    public function iPassThroughTheIdP()
265
    {
266
        $this->minkContext->pressButton('Submit');
267
    }
268
269
    /**
270
     * @Then /^the response should have a SSO\-2FA cookie$/
271
     * @throws ExpectationException
272
     */
273
    public function theResponseShouldHaveASSO2FACookie()
274
    {
275
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
276
        $cookieValue = $this->minkContext->getSession()->getCookie($this->sso2faCookieName);
277
        // Store the previous cookie value
278
        $this->previousSsoOn2faCookieValue = $cookieValue;
279
        $this->validateSsoOn2faCookie($cookieValue);
280
    }
281
282
    /**
283
     * @Then /^the response should not have a SSO\-2FA cookie$/
284
     * @throws ExpectationException
285
     */
286
    public function theResponseShouldNotHaveASSO2FACookie()
287
    {
288
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
289
        $cookie = $this->minkContext->getSession()->getCookie($this->sso2faCookieName);
290
        if (!is_null($cookie)) {
291
            throw new ExpectationException(
292
                'The SSO cookie must NOT be present',
293
                $this->minkContext->getSession()->getDriver()
294
            );
295
        }
296
    }
297
298
    /**
299
     * @Then /^a new SSO\-2FA cookie was written$/
300
     * @throws ExpectationException
301
     */
302
    public function theSSO2FACookieIsRewritten()
303
    {
304
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
305
        $cookieValue = $this->minkContext->getSession()->getCookie($this->sso2faCookieName);
306
        $this->validateSsoOn2faCookie($cookieValue);
307
        if ($this->previousSsoOn2faCookieValue === $cookieValue) {
308
            throw new ExpectationException(
309
                'The SSO on 2FA cookie did not change since the previous response',
310
                $this->minkContext->getSession()->getDriver()
311
            );
312
        }
313
    }
314
315
    /**
316
     * @Then /^the existing SSO\-2FA cookie was used$/
317
     * @throws ExpectationException
318
     */
319
    public function theSSO2FACookieRemainedTheSame()
320
    {
321
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
322
        $cookieValue = $this->minkContext->getSession()->getCookie($this->sso2faCookieName);
323
        $this->validateSsoOn2faCookie($cookieValue);
324
        if ($this->previousSsoOn2faCookieValue !== $cookieValue) {
325
            throw new ExpectationException(
326
                sprintf(
327
                    'The SSO on 2FA cookie changed since the previous response %s vs %s',
328
                    $this->previousSsoOn2faCookieValue,
329
                    $cookieValue
330
                ),
331
                $this->minkContext->getSession()->getDriver()
332
            );
333
        }
334
    }
335
336
    /**
337
     * @Given /^the user cleared cookies from browser$/
338
     */
339
    public function userClearedCookies()
340
    {
341
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
342
        $this->minkContext->getSession()->setCookie($this->sso2faCookieName, null);
343
    }
344
345
    /**
346
     * @Given /^the SSO\-2FA cookie should contain "([^"]*)"$/
347
     */
348
    public function theSSO2FACookieShouldContain($expectedCookieValue)
349
    {
350
        $this->minkContext->visit('https://gateway.stepup.example.com/info');
351
        $cookieValue = $this->minkContext->getSession()->getCookie($this->sso2faCookieName);
352
        if (strstr($cookieValue, $expectedCookieValue) === false) {
0 ignored issues
show
Bug introduced by
It seems like $cookieValue can also be of type null; however, parameter $haystack of strstr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

352
        if (strstr(/** @scrutinizer ignore-type */ $cookieValue, $expectedCookieValue) === false) {
Loading history...
353
            throw new ExpectationException(
354
                sprintf(
355
                    'The SSO on 2FA cookie did not contain the expected value: "%s", actual contents: "%s"',
356
                    $expectedCookieValue,
357
                    $cookieValue
358
                ),
359
                $this->minkContext->getSession()->getDriver()
360
            );
361
        }
362
363
    }
364
365
    private function getCookieNames(array $responseCookieHeaders): array
0 ignored issues
show
Unused Code introduced by
The method getCookieNames() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
366
    {
367
        $response = [];
368
        foreach($responseCookieHeaders as $cookie) {
369
            $parts = explode('=', $cookie);
370
            $response[] = array_shift($parts);
371
        }
372
        return $response;
373
    }
374
375
    /**
376
     * @throws ExpectationException
377
     */
378
    private function validateSsoOn2faCookie(?string $cookieValue)
379
    {
380
        if (empty($cookieValue)) {
381
            throw new ExpectationException(
382
                sprintf(
383
                    'The SSO on 2FA cookie was not present, or empty. Cookie name: %s',
384
                    $this->sso2faCookieName
385
                ),
386
                $this->minkContext->getSession()->getDriver()
387
            );
388
        }
389
    }
390
}
391