Skip to content

Commit c1ec13c

Browse files
committed
MQE-1006: Handling secure/sensitive data in MFTF test
- add new credential manager - add new credentials example file - modify build project command to copy in credentials file
1 parent c66df37 commit c1ec13c

File tree

8 files changed

+215
-15
lines changed

8 files changed

+215
-15
lines changed

dev/tests/verification/Resources/PersistedReplacementTest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class PersistedReplacementTestCest
5252
$I->fillField("#selector", "StringBefore " . $createdData->getCreatedDataByName('firstname') . " StringAfter");
5353
$I->fillField("#" . $createdData->getCreatedDataByName('firstname'), "input");
5454
$I->fillField("#" . getenv("MAGENTO_BASE_URL") . "#" . $createdData->getCreatedDataByName('firstname'), "input");
55+
$I->fillField("#" . CredentialStore::getInstance()->getSecret("SECRET_PARAM") . "#" . $createdData->getCreatedDataByName('firstname'), "input");
5556
$I->dragAndDrop("#" . $createdData->getCreatedDataByName('firstname'), $createdData->getCreatedDataByName('lastname'));
5657
$I->conditionalClick($createdData->getCreatedDataByName('lastname'), "#" . $createdData->getCreatedDataByName('firstname'), true);
5758
$I->amOnUrl($createdData->getCreatedDataByName('firstname') . ".html");

dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<fillField stepKey="inputReplace" selector="#selector" userInput="StringBefore $createdData.firstname$ StringAfter"/>
1818
<fillField stepKey="selectorReplace" selector="#$createdData.firstname$" userInput="input"/>
1919
<fillField stepKey="selectorReplace2" selector="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" userInput="input"/>
20+
<fillField stepKey="selectorReplace3" selector="#{{_CREDS.SECRET_PARAM}}#$createdData.firstname$" userInput="input"/>
2021
<dragAndDrop stepKey="selector12Replace" selector1="#$createdData.firstname$" selector2="$createdData.lastname$"/>
2122
<conditionalClick stepKey="dependentSelectorReplace" dependentSelector="#$createdData.firstname$" selector="$createdData.lastname$" visible="true"/>
2223
<amOnUrl stepKey="urlReplace" url="$createdData.firstname$.html"/>

etc/config/.credentials.example

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#carriers/fedex/account=
2+
#carriers/fedex/meter_number=
3+
#carriers/fedex/key=
4+
#carriers/fedex/password=
5+
6+
#carriers/ups/password=
7+
#carriers/ups/username=
8+
#carriers/ups/access_license_number=
9+
#carriers/ups/shipper_number=
10+
11+
#carriers/usps/userid=
12+
#carriers/usps/password=
13+
14+
#carriers_dhl_id_us=
15+
#carriers_dhl_password_us=
16+
#carriers_dhl_account_us=
17+
18+
#carriers_dhl_id_eu=
19+
#carriers_dhl_password_eu=
20+
#carriers_dhl_account_eu=
21+
22+
23+
#payment_authorizenet_login=
24+
#payment_authorizenet_trans_key=
25+
#payment_authorizenet_trans_md5=
26+
27+
#authorizenet_fraud_review_login=
28+
#authorizenet_fraud_review_trans_key=
29+
#authorizenet_fraud_review_md5=
30+
31+
#braintree_enabled_fraud_merchant_account_id=
32+
#braintree_enabled_fraud_merchant_id=
33+
#braintree_enabled_fraud_public_key=
34+
#braintree_enabled_fraud_private_key=
35+
36+
#braintree_disabled_fraud_merchant_account_id=
37+
#braintree_disabled_fraud_merchant_id=
38+
#braintree_disabled_fraud_public_key=
39+
#braintree_disabled_fraud_private_key=
40+
41+
#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/business_account=
42+
#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_username=
43+
#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_password=
44+
#payment/paypal_group_all_in_one/wpp_usuk/wpp_required_settings/wpp_and_express_checkout/api_signature=
45+
#payment/paypal_express/merchant_id=
46+
47+
#payflow_pro_fraud_protection_enabled_business_account=
48+
#payflow_pro_fraud_protection_enabled_partner=
49+
#payflow_pro_fraud_protection_enabled_user=
50+
#payflow_pro_fraud_protection_enabled_pwd=
51+
#payflow_pro_fraud_protection_enabled_vendor=
52+
53+
#payflow_pro_business_account=
54+
#payflow_pro_partner=
55+
#payflow_pro_user=
56+
#payflow_pro_pwd=
57+
#payflow_pro_vendor=
58+
59+
#payflow_link_business_account_email=
60+
#payflow_link_partner=
61+
#payflow_link_user=
62+
#payflow_link_password=
63+
#payflow_link_vendor=
64+
65+
#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/business_account=
66+
#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_username=
67+
#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_password=
68+
#payment/paypal_group_all_in_one/payments_pro_hosted_solution_with_express_checkout/pphs_required_settings/pphs_required_settings_pphs/api_signature=
69+
70+
#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/business_account=
71+
#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_username=
72+
#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_password=
73+
#payment/paypal_alternative_payment_methods/express_checkout_us/express_checkout_required/express_checkout_required_express_checkout/api_signature=
74+
75+
#fraud_protection/signifyd/api_key=

src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class BuildProjectCommand extends Command
2222
{
2323
const DEFAULT_YAML_INLINE_DEPTH = 10;
24+
const CREDENTIALS_FILE_PATH = TESTS_BP . DIRECTORY_SEPARATOR . '.credentials.example';
2425

2526
/**
2627
* Env processor manages .env files.
@@ -72,6 +73,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
7273
$setupEnvCommand->run($commandInput, $output);
7374

7475

76+
7577
// TODO can we just import the codecept symfony command?
7678
$codeceptBuildCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' build';
7779
$process = new Process($codeceptBuildCommand);
@@ -126,5 +128,12 @@ private function generateConfigFiles(OutputInterface $output)
126128
$output->writeln("functional.suite.yml applied to " .
127129
TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml');
128130
}
131+
132+
$fileSystem->copy(
133+
FW_BP . '/etc/config/.credentials.example',
134+
self::CREDENTIALS_FILE_PATH
135+
);
136+
137+
$output->writeln('.credentials.example successfully applied.');
129138
}
130139
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\FunctionalTestingFramework\DataGenerator\Handlers;
8+
9+
use Magento\FunctionalTestingFramework\Console\BuildProjectCommand;
10+
use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException;
11+
12+
class CredentialStore
13+
{
14+
/**
15+
* Singletone instnace
16+
*
17+
* @var CredentialStore
18+
*/
19+
private static $INSTANCE = null;
20+
21+
/**
22+
* Key/Value paris of credential names and their corresponding values
23+
*
24+
* @var array
25+
*/
26+
private $credentials = [];
27+
28+
/**
29+
* Static singleton getter for CredentialStore Instance
30+
*
31+
* @return CredentialStore
32+
*/
33+
public static function getInstance()
34+
{
35+
if (self::$INSTANCE == null) {
36+
self::$INSTANCE = new CredentialStore();
37+
}
38+
39+
return self::$INSTANCE;
40+
}
41+
42+
/**
43+
* CredentialStore constructor.
44+
*/
45+
private function __construct()
46+
{
47+
$this->readInCredentialsFile();
48+
}
49+
50+
/**
51+
* Returns the value of a secret based on corresponding key
52+
*
53+
* @param string $key
54+
* @return string|null
55+
*/
56+
public function getSecret($key)
57+
{
58+
return $this->credentials[$key] ?? null;
59+
}
60+
61+
/**
62+
* Private function which reads in secret key/values from .credentials file and stores in memory as key/value pair.
63+
*
64+
* @return void
65+
* @throws TestFrameworkException
66+
*/
67+
private function readInCredentialsFile()
68+
{
69+
$credsFilePath = str_replace(
70+
'.credentials.example',
71+
'.credentials',
72+
BuildProjectCommand::CREDENTIALS_FILE_PATH
73+
);
74+
75+
if (!file_exists($credsFilePath)) {
76+
throw new TestFrameworkException(
77+
"Cannot find .credentials file, please create in "
78+
. TESTS_BP . " in order to reference sensitive information"
79+
);
80+
}
81+
82+
$credContents = file($credsFilePath, FILE_IGNORE_NEW_LINES);
83+
foreach ($credContents as $credValue) {
84+
if (substr($credValue, 0, 1) === '#' || empty($credValue)) {
85+
continue;
86+
}
87+
88+
list($key, $value) = explode("=", $credValue);
89+
if (!empty($value)) {
90+
$this->credentials[$key] = $value;
91+
}
92+
}
93+
}
94+
}

src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
class ActionObject
2424
{
2525
const __ENV = "_ENV";
26+
const __CREDS = "_CREDS";
2627
const DATA_ENABLED_ATTRIBUTES = [
2728
"userInput",
2829
"parameterArray",
@@ -500,7 +501,7 @@ private function findAndReplaceReferences($objectHandler, $inputString)
500501
$obj = $objectHandler->getObject($objName);
501502

502503
// Leave {{_ENV.VARIABLE}} references to be replaced in TestGenerator with getenv("VARIABLE")
503-
if ($objName === ActionObject::__ENV) {
504+
if ($objName === ActionObject::__ENV || $objName === ActionObject::__CREDS) {
504505
continue;
505506
}
506507

src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,7 @@ public function putEnvFile(array $config = [])
106106
$envData .= $key . '=' . $value . PHP_EOL;
107107
}
108108

109-
if ($this->envExists) {
110-
file_put_contents($this->envFile, $envData, FILE_APPEND);
111-
} else {
112-
file_put_contents($this->envFile, $envData);
113-
}
109+
file_put_contents($this->envFile, $envData);
114110
}
115111

116112
/**

src/Magento/FunctionalTestingFramework/Util/TestGenerator.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\FunctionalTestingFramework\Util;
88

9+
use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore;
910
use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject;
1011
use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException;
1112
use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler;
@@ -747,7 +748,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = "
747748
$testSteps .= $contextSetter;
748749
$testSteps .= $deleteEntityFunctionCall;
749750
} else {
750-
$url = $this->resolveEnvReferences([$url])[0];
751+
$url = $this->resolveAllRuntimeReferences([$url])[0];
751752
$url = $this->resolveTestVariable([$url], null)[0];
752753
$output = sprintf(
753754
"\t\t$%s->deleteEntityByUrl(%s);\n",
@@ -1675,7 +1676,7 @@ private function wrapFunctionCall($actor, $action, ...$args)
16751676
if (!is_array($args)) {
16761677
$args = [$args];
16771678
}
1678-
$args = $this->resolveEnvReferences($args);
1679+
$args = $this->resolveAllRuntimeReferences($args);
16791680
$args = $this->resolveTestVariable($args, $action->getActionOrigin());
16801681
$output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n";
16811682
return $output;
@@ -1706,7 +1707,7 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio
17061707
if (!is_array($args)) {
17071708
$args = [$args];
17081709
}
1709-
$args = $this->resolveEnvReferences($args);
1710+
$args = $this->resolveAllRuntimeReferences($args);
17101711
$args = $this->resolveTestVariable($args, $action->getActionOrigin());
17111712
$output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n";
17121713
return $output;
@@ -1716,21 +1717,21 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio
17161717
/**
17171718
* Resolves {{_ENV.variable}} into getenv("variable") for test-runtime ENV referencing.
17181719
* @param array $args
1720+
* @param string $regex
1721+
* @param string $func
17191722
* @return array
17201723
*/
1721-
private function resolveEnvReferences($args)
1724+
private function resolveRuntimeReference($args, $regex, $func)
17221725
{
1723-
$envRegex = "/{{_ENV\.([\w]+)}}/";
1724-
17251726
$newArgs = [];
17261727

17271728
foreach ($args as $key => $arg) {
1728-
preg_match_all($envRegex, $arg, $matches);
1729+
preg_match_all($regex, $arg, $matches);
17291730
if (!empty($matches[0])) {
17301731
$fullMatch = $matches[0][0];
1731-
$envVariable = $matches[1][0];
1732+
$refVariable = $matches[1][0];
17321733
unset($matches);
1733-
$replacement = "getenv(\"{$envVariable}\")";
1734+
$replacement = "{$func}(\"{$refVariable}\")";
17341735

17351736
$outputArg = $this->processQuoteBreaks($fullMatch, $arg, $replacement);
17361737
$newArgs[$key] = $outputArg;
@@ -1743,6 +1744,28 @@ private function resolveEnvReferences($args)
17431744
return $newArgs;
17441745
}
17451746

1747+
/**
1748+
* Takes a predefined list of potentially matching special paramts and they needed function replacement and performs
1749+
* replacements on the tests args.
1750+
*
1751+
* @param array $args
1752+
* @return array
1753+
*/
1754+
private function resolveAllRuntimeReferences($args)
1755+
{
1756+
$runtimeReferenceRegex = [
1757+
"/{{_ENV\.([\w]+)}}/" => 'getenv',
1758+
"/{{_CREDS\.([\w]+)}}/" => 'CredentialStore::getInstance()->getSecret'
1759+
];
1760+
1761+
$argResult = $args;
1762+
foreach ($runtimeReferenceRegex as $regex => $func) {
1763+
$argResult = $this->resolveRuntimeReference($argResult, $regex, $func);
1764+
}
1765+
1766+
return $argResult;
1767+
}
1768+
17461769
/**
17471770
* Validates parameter array format, making sure user has enclosed string with square brackets.
17481771
*

0 commit comments

Comments
 (0)