diff --git a/app/code/Magento/Captcha/Model/Observer.php b/app/code/Magento/Captcha/Model/Observer.php index 30417fd063a19..69d63b64c3370 100644 --- a/app/code/Magento/Captcha/Model/Observer.php +++ b/app/code/Magento/Captcha/Model/Observer.php @@ -321,7 +321,9 @@ public function checkUserForgotPasswordBackend($observer) */ public function resetAttemptForFrontend($observer) { - return $this->_getResourceModel()->deleteUserAttempts($observer->getModel()->getEmail()); + /** @var \Magento\Customer\Model\Customer $model */ + $model = $observer->getModel(); + return $this->_getResourceModel()->deleteUserAttempts($model->getEmail()); } /** diff --git a/app/code/Magento/Cms/Setup/InstallData.php b/app/code/Magento/Cms/Setup/InstallData.php index e73fe3d3e6c0d..d85f872e1e285 100644 --- a/app/code/Magento/Cms/Setup/InstallData.php +++ b/app/code/Magento/Cms/Setup/InstallData.php @@ -377,24 +377,6 @@ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $installer->doUpdateClassAliases(); $setup->endSetup(); - - $cookieRestriction = $this->createPage()->load('privacy-policy-cookie-restriction-mode', 'identifier'); - - if ($cookieRestriction->getId()) { - $content = $cookieRestriction->getContent(); - $replacment = '{{config path="general/store_information/street_line1"}} ' . - '{{config path="general/store_information/street_line2"}} ' . - '{{config path="general/store_information/city"}} ' . - '{{config path="general/store_information/postcode"}} ' . - '{{config path="general/store_information/region_id"}} ' . - '{{config path="general/store_information/country_id"}}'; - $content = preg_replace( - '/{{config path="general\\/store_information\\/address"}}/ims', - $replacment, - $content - ); - $cookieRestriction->setContent($content)->save(); - } } /** diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 8cc817d34b0de..2cbe283339dfe 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -32,6 +32,7 @@ protected function _extractCustomerData() CustomerInterface::DEFAULT_BILLING, CustomerInterface::DEFAULT_SHIPPING, 'confirmation', + 'sendemail_store_id', ]; $customerData = $this->_extractData( @@ -226,6 +227,7 @@ public function execute() ['customer' => $customer, 'request' => $request] ); $customer->setAddresses($addresses); + $customer->setStoreId($customerData['sendemail_store_id']); // Save customer if ($isExistingCustomer) { diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 3f3a18d2b3cad..500a104861d3b 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -378,9 +378,10 @@ public function authenticate($username, $password) throw new EmailNotConfirmedException(__('This account is not confirmed.')); } + $customerModel = $this->customerFactory->create()->updateData($customer); $this->eventManager->dispatch( 'customer_customer_authenticated', - ['model' => $this->getFullCustomerObject($customer), 'password' => $password] + ['model' => $customerModel, 'password' => $password] ); $this->eventManager->dispatch('customer_data_object_login', ['customer' => $customer]); diff --git a/app/code/Magento/Customer/Model/Observer.php b/app/code/Magento/Customer/Model/Observer.php index 5763043f7aa49..c709130edb9de 100644 --- a/app/code/Magento/Customer/Model/Observer.php +++ b/app/code/Magento/Customer/Model/Observer.php @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Customer\Model; use Magento\Customer\Api\GroupManagementInterface; @@ -14,12 +12,16 @@ use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\State as AppState; +use Magento\Framework\DataObject; +use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Escaper; +use Magento\Framework\Message\ManagerInterface; use Magento\Framework\Registry; use Magento\Store\Model\ScopeInterface; -use Magento\Framework\Message\ManagerInterface; /** + * Customer Observer Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Observer @@ -74,6 +76,13 @@ class Observer */ protected $appState; + /** + * Encryption model + * + * @var EncryptorInterface + */ + protected $encryptor; + /** * @param Vat $customerVat * @param HelperAddress $customerAddress @@ -83,6 +92,7 @@ class Observer * @param ManagerInterface $messageManager * @param Escaper $escaper * @param AppState $appState + * @param EncryptorInterface $encryptor */ public function __construct( Vat $customerVat, @@ -92,7 +102,8 @@ public function __construct( ScopeConfigInterface $scopeConfig, ManagerInterface $messageManager, Escaper $escaper, - AppState $appState + AppState $appState, + EncryptorInterface $encryptor ) { $this->_customerVat = $customerVat; $this->_customerAddress = $customerAddress; @@ -102,6 +113,7 @@ public function __construct( $this->messageManager = $messageManager; $this->escaper = $escaper; $this->appState = $appState; + $this->encryptor = $encryptor; } /** @@ -251,7 +263,7 @@ public function afterAddressSave($observer) * Add success message for valid VAT ID * * @param Address $customerAddress - * @param $validationResult + * @param DataObject $validationResult * @return $this */ protected function addValidMessage($customerAddress, $validationResult) @@ -326,4 +338,25 @@ protected function addErrorMessage($customerAddress) $this->messageManager->addError(implode(' ', $message)); return $this; } + + /** + * Upgrade customer password hash when customer has logged in + * + * @param EventObserver $observer + * @return void + */ + public function upgradeCustomerPassword($observer) + { + $password = $observer->getEvent()->getPassword(); + /** @var \Magento\Customer\Model\Customer $model */ + $model = $observer->getEvent()->getModel(); + $isValidHash = $this->encryptor->isValidHashByVersion( + $password, + $model->getPasswordHash(), + Encryptor::HASH_VERSION_LATEST + ); + if (!$isValidHash) { + $model->changePassword($password); + } + } } diff --git a/app/code/Magento/Customer/README.md b/app/code/Magento/Customer/README.md index f3de8700173ce..d0463d847b74b 100644 --- a/app/code/Magento/Customer/README.md +++ b/app/code/Magento/Customer/README.md @@ -1 +1,2 @@ -The Magento_Customer module serves to handle the customer data (Customer, Customer Address and Customer Group entities) both in the admin panel and the storefront. +The Magento_Customer module serves to handle the customer data (Customer, Customer Address and Customer Group entities) both in the admin panel and the storefront. +For customer passwords, the module implements upgrading hashes. diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index 372b54adc304e..95c875393ed6a 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -299,6 +299,7 @@ public function testExecuteWithExistentCustomer() \Magento\Customer\Api\Data\CustomerInterface::DEFAULT_BILLING => $addressId, \Magento\Customer\Api\Data\CustomerInterface::DEFAULT_SHIPPING => $addressId, 'confirmation' => false, + 'sendemail_store_id' => '1', 'id' => $customerId, ]; $mergedAddressData = [ diff --git a/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php index d02337fe6bc81..6e50fdaf1f7df 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ObserverTest.php @@ -68,6 +68,16 @@ class ObserverTest extends \PHPUnit_Framework_TestCase */ protected $appState; + /** + * @var \Magento\Framework\Encryption\Encryptor|\PHPUnit_Framework_MockObject_MockObject + */ + protected $encryptorMock; + + /** + * @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject + */ + protected $customerMock; + protected function setUp() { $this->vat = $this->getMockBuilder('Magento\Customer\Model\Vat') @@ -99,6 +109,18 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->encryptorMock = $this->getMockBuilder('\Magento\Framework\Encryption\Encryptor') + ->disableOriginalConstructor() + ->getMock(); + $this->encryptorMock->expects($this->any()) + ->method('isValidHashByVersion') + ->will( + $this->returnCallback( + function ($arg1, $arg2) { + return $arg1 == $arg2; + } + ) + ); $this->model = new Observer( $this->vat, $this->helperAddress, @@ -107,7 +129,8 @@ protected function setUp() $this->scopeConfig, $this->messageManager, $this->escaper, - $this->appState + $this->appState, + $this->encryptorMock ); } @@ -746,4 +769,45 @@ public function dataProviderAfterAddressSaveNewGroup() ], ]; } + + /** + * Test successfully password change if new password doesn't match old one + */ + public function testUpgradeCustomerPassword() + { + $customerMock = $this->getMockBuilder('\Magento\Customer\Model\Customer') + ->disableOriginalConstructor() + ->setMethods(['getPasswordHash', 'changePassword', '__wakeup']) + ->getMock(); + $customerMock->expects($this->once())->method('changePassword')->will($this->returnSelf()); + $customerMock->expects($this->once())->method('getPasswordHash')->will($this->returnValue('old password')); + + $event = new \Magento\Framework\DataObject(); + $event->setData(['password' => 'different password', 'model' => $customerMock]); + + $observerMock = new \Magento\Framework\DataObject(); + $observerMock->setData('event', $event); + + $this->model->upgradeCustomerPassword($observerMock); + } + + /** + * Test failure password change if new password matches old one + */ + public function testUpgradeCustomerPasswordNotChanged() + { + $customerMock = $this->getMockBuilder('\Magento\Customer\Model\Customer') + ->disableOriginalConstructor() + ->setMethods(['getPasswordHash', 'changePassword', '__wakeup']) + ->getMock(); + $customerMock->expects($this->never())->method('changePassword'); + $customerMock->expects($this->once())->method('getPasswordHash')->will($this->returnValue('same password')); + + $event = new \Magento\Framework\DataObject(); + $event->setData(['password' => 'same password', 'model' => $customerMock]); + + $observerMock = new \Magento\Framework\DataObject(); + $observerMock->setData('event', $event); + $this->model->upgradeCustomerPassword($observerMock); + } } diff --git a/app/code/Magento/Customer/etc/frontend/events.xml b/app/code/Magento/Customer/etc/frontend/events.xml index 51e6bec7b9c7e..826f91980b4f1 100644 --- a/app/code/Magento/Customer/etc/frontend/events.xml +++ b/app/code/Magento/Customer/etc/frontend/events.xml @@ -25,4 +25,7 @@ + + + diff --git a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml index a32b3dee3716b..4a4305c82fd4d 100644 --- a/app/code/Magento/Customer/view/base/ui_component/customer_form.xml +++ b/app/code/Magento/Customer/view/base/ui_component/customer_form.xml @@ -245,6 +245,16 @@ + + + Magento\Store\Model\System\Store + + Send Welcome Email From + number + select + + +
diff --git a/app/code/Magento/Email/Model/Source/Variables.php b/app/code/Magento/Email/Model/Source/Variables.php index e734627f770b2..d18c3ba433c42 100644 --- a/app/code/Magento/Email/Model/Source/Variables.php +++ b/app/code/Magento/Email/Model/Source/Variables.php @@ -72,4 +72,15 @@ public function toOptionArray($withGroup = false) } return $optionArray; } + + /** + * Return available config variables + * + * @return array + * @codeCoverageIgnore + */ + public function getData() + { + return $this->_configVariables; + } } diff --git a/app/code/Magento/Email/Model/Template/Filter.php b/app/code/Magento/Email/Model/Template/Filter.php index 80ae41d59d72d..e8a5071508544 100644 --- a/app/code/Magento/Email/Model/Template/Filter.php +++ b/app/code/Magento/Email/Model/Template/Filter.php @@ -145,6 +145,11 @@ class Filter extends \Magento\Framework\Filter\Template */ protected $emogrifier; + /** + * @var \Magento\Email\Model\Source\Variables + */ + protected $configVariables; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Psr\Log\LoggerInterface $logger @@ -158,6 +163,7 @@ class Filter extends \Magento\Framework\Filter\Template * @param \Magento\Framework\App\State $appState * @param \Magento\Framework\UrlInterface $urlModel * @param \Pelago\Emogrifier $emogrifier + * @param \Magento\Email\Model\Source\Variables $configVariables * @param array $variables * * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -175,6 +181,7 @@ public function __construct( \Magento\Framework\App\State $appState, \Magento\Framework\UrlInterface $urlModel, \Pelago\Emogrifier $emogrifier, + \Magento\Email\Model\Source\Variables $configVariables, $variables = [] ) { $this->_escaper = $escaper; @@ -189,6 +196,7 @@ public function __construct( $this->_appState = $appState; $this->urlModel = $urlModel; $this->emogrifier = $emogrifier; + $this->configVariables = $configVariables; parent::__construct($string, $variables); } @@ -225,7 +233,7 @@ public function setUseSessionInUrl($flag) */ public function setPlainTemplateMode($plainTemplateMode) { - $this->plainTemplateMode = (bool) $plainTemplateMode; + $this->plainTemplateMode = (bool)$plainTemplateMode; return $this; } @@ -247,7 +255,7 @@ public function isPlainTemplateMode() */ public function setIsChildTemplate($isChildTemplate) { - $this->isChildTemplate = (bool) $isChildTemplate; + $this->isChildTemplate = (bool)$isChildTemplate; return $this; } @@ -703,7 +711,7 @@ public function configDirective($construction) $configValue = ''; $params = $this->getParameters($construction[2]); $storeId = $this->getStoreId(); - if (isset($params['path'])) { + if (isset($params['path']) && $this->isAvailableConfigVariable($params['path'])) { $configValue = $this->_scopeConfig->getValue( $params['path'], \Magento\Store\Model\ScopeInterface::SCOPE_STORE, @@ -713,6 +721,20 @@ public function configDirective($construction) return $configValue; } + /** + * Check if given variable is available for directive "Config" + * + * @param string $variable + * @return bool + */ + private function isAvailableConfigVariable($variable) + { + return in_array( + $variable, + array_column($this->configVariables->getData(), 'value') + ); + } + /** * Custom Variable directive * diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php index 2abe060d932b3..a21a6dbd30bf1 100644 --- a/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/Template/FilterTest.php @@ -70,6 +70,11 @@ class FilterTest extends \PHPUnit_Framework_TestCase */ private $backendUrlBuilder; + /** + * @var \Magento\Email\Model\Source\Variables|\PHPUnit_Framework_MockObject_MockObject + */ + private $configVariables; + /** * @var \Pelago\Emogrifier */ @@ -125,6 +130,10 @@ protected function setUp() ->getMock(); $this->emogrifier = $this->objectManager->getObject('\Pelago\Emogrifier'); + + $this->configVariables = $this->getMockBuilder('Magento\Email\Model\Source\Variables') + ->disableOriginalConstructor() + ->getMock(); } /** @@ -147,6 +156,7 @@ protected function getModel($mockedMethods = null) $this->appState, $this->backendUrlBuilder, $this->emogrifier, + $this->configVariables, [], ]) ->setMethods($mockedMethods) @@ -330,4 +340,46 @@ public function testAfterFilterCallbackGetsResetWhenExceptionTriggered() $this->assertEquals($exceptionResult, $filter->filter($value)); } + + public function testConfigDirectiveAvailable() + { + $path = "web/unsecure/base_url"; + $availableConfigs = [['value' => $path]]; + $construction = ["{{config path={$path}}}", 'config', " path={$path}"]; + $scopeConfigValue = 'value'; + + $storeMock = $this->getMock('Magento\Store\Api\Data\StoreInterface', [], [], '', false); + $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->once())->method('getId')->willReturn(1); + + $this->configVariables->expects($this->once()) + ->method('getData') + ->willReturn($availableConfigs); + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->willReturn($scopeConfigValue); + + $this->assertEquals($scopeConfigValue, $this->getModel()->configDirective($construction)); + } + + public function testConfigDirectiveUnavailable() + { + $path = "web/unsecure/base_url"; + $availableConfigs = []; + $construction = ["{{config path={$path}}}", 'config', " path={$path}"]; + $scopeConfigValue = ''; + + $storeMock = $this->getMock('Magento\Store\Api\Data\StoreInterface', [], [], '', false); + $this->storeManager->expects($this->once())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->once())->method('getId')->willReturn(1); + + $this->configVariables->expects($this->once()) + ->method('getData') + ->willReturn($availableConfigs); + $this->scopeConfig->expects($this->never()) + ->method('getValue') + ->willReturn($scopeConfigValue); + + $this->assertEquals($scopeConfigValue, $this->getModel()->configDirective($construction)); + } } diff --git a/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Edit.php b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Edit.php new file mode 100644 index 0000000000000..5ce0c2da40a5f --- /dev/null +++ b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Edit.php @@ -0,0 +1,57 @@ +buttonList->add( + 'save', + [ + 'label' => __('Change Encryption Key'), + 'class' => 'save primary save-encryption-key', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save', 'target' => '#edit_form']], + ] + ], + 1 + ); + } + + /** + * Header text getter + * + * @return \Magento\Framework\Phrase + */ + public function getHeaderText() + { + return __('Encryption Key'); + } +} diff --git a/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php new file mode 100644 index 0000000000000..7cce8004d0acc --- /dev/null +++ b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php @@ -0,0 +1,61 @@ +_formFactory->create( + [ + 'data' => [ + 'id' => 'edit_form', + 'action' => $this->getData('action'), + 'method' => 'post' + ] + ] + ); + $fieldset = $form->addFieldset('main_fieldset', ['legend' => __('New Encryption Key')]); + $fieldset->addField( + 'enc_key_note', + 'note', + ['text' => __('The encryption key is used to protect passwords and other sensitive data.')] + ); + $fieldset->addField( + 'generate_random', + 'select', + [ + 'name' => 'generate_random', + 'label' => __('Auto-generate a Key'), + 'options' => [0 => __('No'), 1 => __('Yes')], + 'onclick' => "var cryptKey = $('crypt_key'); cryptKey.disabled = this.value == 1; " . + "if (cryptKey.disabled) {cryptKey.parentNode.parentNode.hide();} " . + "else {cryptKey.parentNode.parentNode.show();}", + 'note' => __('The generated key will be displayed after changing.') + ] + ); + $fieldset->addField( + 'crypt_key', + 'text', + ['name' => 'crypt_key', 'label' => __('New Key'), 'style' => 'width:32em;', 'maxlength' => 32] + ); + $form->setUseContainer(true); + if ($data = $this->getFormData()) { + $form->addValues($data); + } + $this->setForm($form); + return parent::_prepareForm(); + } +} diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php new file mode 100644 index 0000000000000..8c02fcacecb6e --- /dev/null +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key.php @@ -0,0 +1,23 @@ +_authorization->isAllowed('Magento_EncryptionKey::crypt_key'); + } +} diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php new file mode 100644 index 0000000000000..0c2ad5395a93e --- /dev/null +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php @@ -0,0 +1,39 @@ +_objectManager->get('Magento\Framework\App\DeploymentConfig\Writer'); + if (!$writer->checkIfWritable()) { + $this->messageManager->addError(__('Deployment configuration file is not writable.')); + } + + $this->_view->loadLayout(); + $this->_setActiveMenu('Magento_EncryptionKey::system_crypt_key'); + $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Encryption Key')); + + if (($formBlock = $this->_view->getLayout()->getBlock('crypt.key.form')) && + ($data = $this->_objectManager->get('Magento\Backend\Model\Session')->getFormData(true))) { + /* @var \Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Form $formBlock */ + $formBlock->setFormData($data); + } + + $this->_view->renderLayout(); + } +} diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php new file mode 100644 index 0000000000000..b294d98df9c1c --- /dev/null +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Save.php @@ -0,0 +1,85 @@ +encryptor = $encryptor; + $this->change = $change; + $this->cache = $cache; + parent::__construct($context); + } + + /** + * Process saving new encryption key + * + * @return void + */ + public function execute() + { + try { + $key = null; + + if (0 == $this->getRequest()->getPost('generate_random')) { + $key = $this->getRequest()->getPost('crypt_key'); + if (empty($key)) { + throw new \Exception(__('Please enter an encryption key.')); + } + $this->encryptor->validateKey($key); + } + + $newKey = $this->change->changeEncryptionKey($key); + $this->messageManager->addSuccess(__('The encryption key has been changed.')); + + if (!$key) { + $this->messageManager->addNotice( + __( + 'This is your new encryption key: %1. ' . + 'Be sure to write it down and take good care of it!', + $newKey + ) + ); + } + $this->cache->clean(); + } catch (\Exception $e) { + $this->messageManager->addError($e->getMessage()); + $this->_session->setFormData(['crypt_key' => $key]); + } + $this->_redirect('adminhtml/*/'); + } +} diff --git a/app/code/Magento/EncryptionKey/LICENSE.txt b/app/code/Magento/EncryptionKey/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/EncryptionKey/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/EncryptionKey/LICENSE_AFL.txt b/app/code/Magento/EncryptionKey/LICENSE_AFL.txt new file mode 100644 index 0000000000000..87943b95d43a5 --- /dev/null +++ b/app/code/Magento/EncryptionKey/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php new file mode 100644 index 0000000000000..2ebb4e2cc3f8e --- /dev/null +++ b/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php @@ -0,0 +1,202 @@ +encryptor = clone $encryptor; + parent::__construct($context, $connectionName); + $this->directory = $filesystem->getDirectoryWrite(DirectoryList::CONFIG); + $this->structure = $structure; + $this->writer = $writer; + } + + /** + * Initialize + * + * @return void + */ + protected function _construct() + { + $this->_init('core_config_data', 'config_id'); + } + + /** + * Re-encrypt all encrypted data in the database + * + * TODO: seems not used + * + * @param bool $safe Specifies whether wrapping re-encryption into the database transaction or not + * @return void + * @throws \Exception + */ + public function reEncryptDatabaseValues($safe = true) + { + // update database only + if ($safe) { + $this->beginTransaction(); + } + try { + $this->_reEncryptSystemConfigurationValues(); + $this->_reEncryptCreditCardNumbers(); + if ($safe) { + $this->commit(); + } + } catch (\Exception $e) { + if ($safe) { + $this->rollBack(); + } + throw $e; + } + } + + /** + * Change encryption key + * + * @param string|null $key + * @return null|string + * @throws \Exception + */ + public function changeEncryptionKey($key = null) + { + // prepare new key, encryptor and new configuration segment + if (!$this->writer->checkIfWritable()) { + throw new \Exception(__('Deployment configuration file is not writable.')); + } + + if (null === $key) { + $key = md5(time()); + } + $this->encryptor->setNewKey($key); + + $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); + $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); + + $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; + + // update database and config.php + $this->beginTransaction(); + try { + $this->_reEncryptSystemConfigurationValues(); + $this->_reEncryptCreditCardNumbers(); + $this->writer->saveConfig($configData); + $this->commit(); + return $key; + } catch (\Exception $e) { + $this->rollBack(); + throw $e; + } + } + + /** + * Gather all encrypted system config values and re-encrypt them + * + * @return void + */ + protected function _reEncryptSystemConfigurationValues() + { + // look for encrypted node entries in all system.xml files + /** @var \Magento\Config\Model\Config\Structure $configStructure */ + $configStructure = $this->structure; + $paths = $configStructure->getFieldPathsByAttribute( + 'backend_model', + 'Magento\Config\Model\Config\Backend\Encrypted' + ); + + // walk through found data and re-encrypt it + if ($paths) { + $table = $this->getTable('core_config_data'); + $values = $this->getConnection()->fetchPairs( + $this->getConnection() + ->select() + ->from($table, ['config_id', 'value']) + ->where('path IN (?)', $paths) + ->where('value NOT LIKE ?', '') + ); + foreach ($values as $configId => $value) { + $this->getConnection()->update( + $table, + ['value' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], + ['config_id = ?' => (int)$configId] + ); + } + } + } + + /** + * Gather saved credit card numbers from sales order payments and re-encrypt them + * + * @return void + */ + protected function _reEncryptCreditCardNumbers() + { + $table = $this->getTable('sales_order_payment'); + $select = $this->getConnection()->select()->from($table, ['entity_id', 'cc_number_enc']); + + $attributeValues = $this->getConnection()->fetchPairs($select); + // save new values + foreach ($attributeValues as $valueId => $value) { + $this->getConnection()->update( + $table, + ['cc_number_enc' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], + ['entity_id = ?' => (int)$valueId] + ); + } + } +} diff --git a/app/code/Magento/EncryptionKey/README.md b/app/code/Magento/EncryptionKey/README.md new file mode 100644 index 0000000000000..b90ea34fdf478 --- /dev/null +++ b/app/code/Magento/EncryptionKey/README.md @@ -0,0 +1 @@ +The Magento_EncryptionKey module provides an advanced encryption model to protect passwords and other sensitive data. diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json new file mode 100644 index 0000000000000..968d9155e504f --- /dev/null +++ b/app/code/Magento/EncryptionKey/composer.json @@ -0,0 +1,25 @@ +{ + "name": "magento/module-encryption-key", + "description": "N/A", + "require": { + "php": "~5.5.0|~5.6.0", + "magento/module-config": "1.0.0-beta", + "magento/module-sales": "1.0.0-beta", + "magento/module-backend": "1.0.0-beta", + "magento/framework": "1.0.0-beta", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-module", + "version": "1.0.0-beta", + "license": [ + "proprietary" + ], + "extra": { + "map": [ + [ + "*", + "Magento/EncryptionKey" + ] + ] + } +} diff --git a/app/code/Magento/EncryptionKey/etc/acl.xml b/app/code/Magento/EncryptionKey/etc/acl.xml new file mode 100644 index 0000000000000..15dcb594e635b --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/acl.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/app/code/Magento/EncryptionKey/etc/adminhtml/menu.xml b/app/code/Magento/EncryptionKey/etc/adminhtml/menu.xml new file mode 100644 index 0000000000000..df50e3ce95718 --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/adminhtml/menu.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/app/code/Magento/EncryptionKey/etc/adminhtml/routes.xml b/app/code/Magento/EncryptionKey/etc/adminhtml/routes.xml new file mode 100644 index 0000000000000..4269e609c50cd --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/EncryptionKey/etc/config.xml b/app/code/Magento/EncryptionKey/etc/config.xml new file mode 100644 index 0000000000000..204937c0b0ae5 --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/config.xml @@ -0,0 +1,16 @@ + + + + + + + 900 + + + + diff --git a/app/code/Magento/EncryptionKey/etc/module.xml b/app/code/Magento/EncryptionKey/etc/module.xml new file mode 100644 index 0000000000000..b30faf3e0646e --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/module.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/app/code/Magento/EncryptionKey/i18n/de_DE.csv b/app/code/Magento/EncryptionKey/i18n/de_DE.csv new file mode 100644 index 0000000000000..6f364baa1ff9f --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/de_DE.csv @@ -0,0 +1,19 @@ +ID,Benutzerkennung +No,No +Yes,Yes +Username,Benutzername +"Encryption Key","Encryption Key" +"Change Encryption Key","Verschlüsselungscode ändern" +"New Encryption Key","Neuer Verschlüsselungscode" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Automatische Schlüsselgenerierung" +"The generated key will be displayed after changing.","Der generierte Schlüssel wird nach seiner Änderung angezeigt." +"New Key","Neuer Schlüssel" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Bitte einen Verschlüsselungscode eingeben." +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Änderung des Verschlüsselungscodes" diff --git a/app/code/Magento/EncryptionKey/i18n/en_US.csv b/app/code/Magento/EncryptionKey/i18n/en_US.csv new file mode 100644 index 0000000000000..792bc2f60f5a8 --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/en_US.csv @@ -0,0 +1,19 @@ +ID,ID +No,No +Yes,Yes +Username,Username +"Encryption Key","Encryption Key" +"Change Encryption Key","Change Encryption Key" +"New Encryption Key","New Encryption Key" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Auto-generate a Key" +"The generated key will be displayed after changing.","The generated key will be displayed after changing." +"New Key","New Key" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Please enter an encryption key." +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Encryption Key Change" diff --git a/app/code/Magento/EncryptionKey/i18n/es_ES.csv b/app/code/Magento/EncryptionKey/i18n/es_ES.csv new file mode 100644 index 0000000000000..0840d0cb0b5b2 --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/es_ES.csv @@ -0,0 +1,19 @@ +ID,Identificación +No,No +Yes,Yes +Username,"Nombre de Usuario" +"Encryption Key","Encryption Key" +"Change Encryption Key","Cambiar clave de encriptación" +"New Encryption Key","Nueva clave de encriptación" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Auto generar una clave" +"The generated key will be displayed after changing.","La clave generada se mostrará después de cambiarla." +"New Key","Nueva clave" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Por favor introduzca una clave de encriptación" +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Cambio de clave de encriptación" diff --git a/app/code/Magento/EncryptionKey/i18n/fr_FR.csv b/app/code/Magento/EncryptionKey/i18n/fr_FR.csv new file mode 100644 index 0000000000000..0604b96989aef --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/fr_FR.csv @@ -0,0 +1,19 @@ +ID,Identifiant +No,No +Yes,Yes +Username,"Nom d'utilisateur" +"Encryption Key","Encryption Key" +"Change Encryption Key","Modifier Clé de Chiffrement" +"New Encryption Key","Nouvelle Clé de Chiffrement" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Générer automatiquement une Clé" +"The generated key will be displayed after changing.","La clé générée apparaîtra après changement." +"New Key","Nouvelle Clé" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Merci d'entrer une clé de chiffrement" +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Modification Clé de Chiffrement" diff --git a/app/code/Magento/EncryptionKey/i18n/nl_NL.csv b/app/code/Magento/EncryptionKey/i18n/nl_NL.csv new file mode 100644 index 0000000000000..cffae21b4184b --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/nl_NL.csv @@ -0,0 +1,19 @@ +ID,identiteit +No,No +Yes,Yes +Username,Gebruikersnaam +"Encryption Key","Encryption Key" +"Change Encryption Key","Verander Encryptie Code" +"New Encryption Key","Nieuwe Encryptie Code" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Automatisch een sleutel genereren." +"The generated key will be displayed after changing.","De gegenereerde sleutel wordt weergegeven na het veranderen." +"New Key","Nieuwe Code" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Vul een encryptie sleutel in." +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Encryptie Code Veranderen" diff --git a/app/code/Magento/EncryptionKey/i18n/pt_BR.csv b/app/code/Magento/EncryptionKey/i18n/pt_BR.csv new file mode 100644 index 0000000000000..495e0c38d96cf --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/pt_BR.csv @@ -0,0 +1,19 @@ +ID,Identidade +No,No +Yes,Yes +Username,"Nome do usuário" +"Encryption Key","Encryption Key" +"Change Encryption Key","Mudar Chave de Criptografia" +"New Encryption Key","Nova Chave de Criptografia" +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key","Autogerar Chave" +"The generated key will be displayed after changing.","A chave gerada será exibida após a mudança." +"New Key","Nova Chave" +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.","Por favor introduza uma chave de encriptação." +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change","Mudança na Chave de Criptografia" diff --git a/app/code/Magento/EncryptionKey/i18n/zh_CN.csv b/app/code/Magento/EncryptionKey/i18n/zh_CN.csv new file mode 100644 index 0000000000000..3b5711dc0af9b --- /dev/null +++ b/app/code/Magento/EncryptionKey/i18n/zh_CN.csv @@ -0,0 +1,19 @@ +ID,ID +No,No +Yes,Yes +Username,用户名 +"Encryption Key","Encryption Key" +"Change Encryption Key",更改加密密钥 +"New Encryption Key",新建加密密钥 +"The encryption key is used to protect passwords and other sensitive data.","The encryption key is used to protect passwords and other sensitive data." +"Auto-generate a Key",自动生成密钥 +"The generated key will be displayed after changing.",生成的密钥会在改动后显示。 +"New Key",新密钥 +"To enable a key change this file must be writable: %1.","To enable a key change this file must be writable: %1." +"Please enter an encryption key.",请输入加密密钥。 +"The encryption key has been changed.","The encryption key has been changed." +"This is your new encryption key: %1. Be sure to write it down and take good care of it!","This is your new encryption key: %1. Be sure to write it down and take good care of it!" +"Not supported cipher version","Not supported cipher version" +"The encryption key format is invalid.","The encryption key format is invalid." +"File %1 is not writeable.","File %1 is not writeable." +"Encryption Key Change",加密密钥已更改 diff --git a/app/code/Magento/EncryptionKey/view/adminhtml/layout/adminhtml_crypt_key_index.xml b/app/code/Magento/EncryptionKey/view/adminhtml/layout/adminhtml_crypt_key_index.xml new file mode 100644 index 0000000000000..7180d426aceb8 --- /dev/null +++ b/app/code/Magento/EncryptionKey/view/adminhtml/layout/adminhtml_crypt_key_index.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json index 86ae74d5e2ae3..87c4889fb8f8f 100644 --- a/app/code/Magento/ImportExport/composer.json +++ b/app/code/Magento/ImportExport/composer.json @@ -7,6 +7,7 @@ "magento/module-backend": "1.0.0-beta", "magento/module-eav": "1.0.0-beta", "magento/module-media-storage": "1.0.0-beta", + "magento/module-user": "1.0.0-beta", "magento/framework": "1.0.0-beta", "ext-ctype": "*", "magento/magento-composer-installer": "*" diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php index 7ad693c280c26..5356a8e953483 100644 --- a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php +++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php @@ -7,7 +7,6 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Customer\Model\Session; -use Magento\SalesRule\Model\Resource\Rule; class QuoteConfigProductAttributes { @@ -19,7 +18,7 @@ class QuoteConfigProductAttributes /** * @param Rule $ruleResource */ - public function __construct(Rule $ruleResource) + public function __construct(\Magento\SalesRule\Model\Resource\Rule $ruleResource) { $this->_ruleResource = $ruleResource; } diff --git a/app/code/Magento/User/Block/Adminhtml/Locks.php b/app/code/Magento/User/Block/Adminhtml/Locks.php new file mode 100644 index 0000000000000..40001b80f054f --- /dev/null +++ b/app/code/Magento/User/Block/Adminhtml/Locks.php @@ -0,0 +1,24 @@ +buttonList->remove('add'); + } +} diff --git a/app/code/Magento/User/Console/UnlockAdminAccountCommand.php b/app/code/Magento/User/Console/UnlockAdminAccountCommand.php new file mode 100644 index 0000000000000..30c3a59956596 --- /dev/null +++ b/app/code/Magento/User/Console/UnlockAdminAccountCommand.php @@ -0,0 +1,85 @@ +adminUser = $adminUser; + parent::__construct($name); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $adminUserName = $input->getArgument(self::ARGUMENT_ADMIN_USERNAME); + $userData = $this->adminUser->loadByUsername($adminUserName); + $outputMessage = sprintf('Couldn\'t find the user account "%s"', $adminUserName); + if ($userData) { + if (isset($userData[self::USER_ID]) && $this->adminUser->unlock($userData[self::USER_ID])) { + $outputMessage = sprintf('The user account "%s" has been unlocked', $adminUserName); + } else { + $outputMessage = sprintf( + 'The user account "%s" was not locked or could not be unlocked', + $adminUserName + ); + } + } + $output->writeln('' . $outputMessage . ''); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName(self::COMMAND_ADMIN_ACCOUNT_UNLOCK); + $this->setDescription(self::COMMAND_DESCRIPTION); + $this->addArgument( + self::ARGUMENT_ADMIN_USERNAME, + InputArgument::REQUIRED, + self::ARGUMENT_ADMIN_USERNAME_DESCRIPTION + ); + $this->setHelp( + <<%command.full_name% username +HELP + ); + parent::configure(); + } +} diff --git a/app/code/Magento/User/Controller/Adminhtml/Locks.php b/app/code/Magento/User/Controller/Adminhtml/Locks.php new file mode 100644 index 0000000000000..5317287a97c05 --- /dev/null +++ b/app/code/Magento/User/Controller/Adminhtml/Locks.php @@ -0,0 +1,23 @@ +_authorization->isAllowed('Magento_User::locks'); + } +} diff --git a/app/code/Magento/User/Controller/Adminhtml/Locks/Grid.php b/app/code/Magento/User/Controller/Adminhtml/Locks/Grid.php new file mode 100644 index 0000000000000..971b87791f613 --- /dev/null +++ b/app/code/Magento/User/Controller/Adminhtml/Locks/Grid.php @@ -0,0 +1,24 @@ +_view->loadLayout(false); + $this->_view->renderLayout(); + } +} diff --git a/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php b/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php new file mode 100644 index 0000000000000..b3e9f092fd1b1 --- /dev/null +++ b/app/code/Magento/User/Controller/Adminhtml/Locks/Index.php @@ -0,0 +1,26 @@ +_view->loadLayout(); + $this->_setActiveMenu('Magento_User::system_acl_locks'); + $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Locked Users')); + $this->_view->renderLayout(); + } +} diff --git a/app/code/Magento/User/Controller/Adminhtml/Locks/MassUnlock.php b/app/code/Magento/User/Controller/Adminhtml/Locks/MassUnlock.php new file mode 100644 index 0000000000000..b87e13d7781ae --- /dev/null +++ b/app/code/Magento/User/Controller/Adminhtml/Locks/MassUnlock.php @@ -0,0 +1,40 @@ +getRequest()->getPost('unlock'); + if ($userIds && is_array($userIds)) { + $affectedUsers = $this->_objectManager + ->get('Magento\User\Model\Resource\User') + ->unlock($userIds); + $this->getMessageManager()->addSuccess(__('Unlocked %1 user(s).', $affectedUsers)); + } + } catch (\Exception $e) { + $this->messageManager->addError($e->getMessage()); + } + + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + return $resultRedirect->setPath('adminhtml/*/'); + } +} diff --git a/app/code/Magento/User/Model/Backend/Observer.php b/app/code/Magento/User/Model/Backend/Observer.php new file mode 100644 index 0000000000000..07fb1a45fd42c --- /dev/null +++ b/app/code/Magento/User/Model/Backend/Observer.php @@ -0,0 +1,389 @@ +authorization = $authorization; + $this->backendConfig = $backendConfig; + $this->userResource = $userResource; + $this->url = $url; + $this->session = $session; + $this->authSession = $authSession; + $this->userFactory = $userFactory; + $this->encryptor = $encryptor; + $this->actionFlag = $actionFlag; + $this->messageManager = $messageManager; + } + + /** + * Admin locking and password hashing upgrade logic implementation + * + * @param EventObserver $observer + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function adminAuthenticate($observer) + { + $password = $observer->getEvent()->getPassword(); + $user = $observer->getEvent()->getUser(); + $authResult = $observer->getEvent()->getResult(); + + if (!$authResult && $user->getId()) { + // update locking information regardless whether user locked or not + $this->_updateLockingInformation($user); + } + + // check whether user is locked + $lockExpires = $user->getLockExpires(); + if ($lockExpires) { + $lockExpires = new \DateTime($lockExpires); + if ($lockExpires > new \DateTime()) { + throw new UserLockedException( + __('You did not sign in correctly or your account is temporarily disabled.') + ); + } + } + + if (!$authResult) { + return; + } + + $this->userResource->unlock($user->getId()); + + $latestPassword = $this->userResource->getLatestPassword($user->getId()); + $this->_checkExpiredPassword($latestPassword); + + // upgrade admin password + $isValidHash = $this->encryptor->isValidHashByVersion( + $password, + $user->getPassword(), + Encryptor::HASH_VERSION_LATEST + ); + if (!$isValidHash) { + $this->userFactory->create() + ->load($user->getId()) + ->setNewPassword($password) + ->setForceNewPassword(true) + ->save(); + } + } + + /** + * Update locking information for the user + * + * @param \Magento\User\Model\User $user + * @return void + */ + private function _updateLockingInformation($user) + { + $now = new \DateTime(); + $lockThreshold = $this->getAdminLockThreshold(); + $maxFailures = (int)$this->backendConfig->getValue('admin/security/lockout_failures'); + if (!($lockThreshold && $maxFailures)) { + return; + } + $failuresNum = (int)$user->getFailuresNum() + 1; + if ($firstFailureDate = $user->getFirstFailure()) { + $firstFailureDate = new \DateTime($firstFailureDate); + } + + $updateFirstFailureDate = false; + $updateLockExpires = false; + $lockThresholdInterval = new \DateInterval('PT' . $lockThreshold.'S'); + // set first failure date when this is first failure or last first failure expired + if (1 === $failuresNum || !$firstFailureDate || $now->diff($firstFailureDate) > $lockThresholdInterval) { + $updateFirstFailureDate = $now; + // otherwise lock user + } elseif ($failuresNum >= $maxFailures) { + $updateLockExpires = $now->add($lockThresholdInterval); + } + $this->userResource->updateFailure($user, $updateLockExpires, $updateFirstFailureDate); + } + + /** + * Check whether the latest password is expired + * Side-effect can be when passwords were changed with different lifetime configuration settings + * + * @param array $latestPassword + * @return void + */ + private function _checkExpiredPassword($latestPassword) + { + if ($latestPassword && $this->_isLatestPasswordExpired($latestPassword)) { + if ($this->isPasswordChangeForced()) { + $message = __('It\'s time to change your password.'); + } else { + $myAccountUrl = $this->url->getUrl('adminhtml/system_account/'); + $message = __('It\'s time to change your password.', $myAccountUrl); + } + $this->messageManager->addNotice($message); + $message = $this->messageManager->getMessages()->getLastAddedMessage(); + if ($message) { + $message->setIdentifier('magento_user_password_expired')->setIsSticky(true); + $this->authSession->setPciAdminUserIsPasswordExpired(true); + } + } + } + + /** + * Check if latest password is expired + * + * @param array $latestPassword + * @return bool + */ + protected function _isLatestPasswordExpired($latestPassword) + { + if (!isset($latestPassword['expires']) || $this->getAdminPasswordLifetime() == 0) { + return false; + } else { + return (int)$latestPassword['expires'] < time(); + } + } + + /** + * Harden admin password change. + * + * New password must be minimum 7 chars length and include alphanumeric characters + * The password is compared to at least last 4 previous passwords to prevent setting them again + * + * @param EventObserver $observer + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function checkAdminPasswordChange($observer) + { + /* @var $user \Magento\User\Model\User */ + $user = $observer->getEvent()->getObject(); + + if ($user->getNewPassword()) { + $password = $user->getNewPassword(); + } else { + $password = $user->getPassword(); + } + + if ($password && !$user->getForceNewPassword() && $user->getId()) { + if ($this->encryptor->validateHash($password, $user->getOrigData('password'))) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Sorry, but this password has already been used. Please create another.') + ); + } + + // check whether password was used before + $resource = $this->userResource; + $passwordHash = $this->encryptor->getHash($password, false); + foreach ($resource->getOldPasswords($user) as $oldPasswordHash) { + if ($passwordHash === $oldPasswordHash) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Sorry, but this password has already been used. Please create another.') + ); + } + } + } + } + + /** + * Save new admin password + * + * @param EventObserver $observer + * @return void + */ + public function trackAdminNewPassword($observer) + { + /* @var $user \Magento\User\Model\User */ + $user = $observer->getEvent()->getObject(); + if ($user->getId()) { + $password = $user->getNewPassword(); + $passwordLifetime = $this->getAdminPasswordLifetime(); + if ($passwordLifetime && $password && !$user->getForceNewPassword()) { + $resource = $this->userResource; + $passwordHash = $this->encryptor->getHash($password, false); + $resource->trackPassword($user, $passwordHash, $passwordLifetime); + $this->messageManager->getMessages()->deleteMessageByIdentifier('magento_user_password_expired'); + $this->authSession->unsPciAdminUserIsPasswordExpired(); + } + } + } + + /** + * Get admin lock threshold from configuration + * @return int + */ + public function getAdminLockThreshold() + { + return 60 * (int)$this->backendConfig->getValue('admin/security/lockout_threshold'); + } + + /** + * Get admin password lifetime + * + * @return int + */ + public function getAdminPasswordLifetime() + { + return 86400 * (int)$this->backendConfig->getValue('admin/security/password_lifetime'); + } + + /** + * Force admin to change password + * + * @param EventObserver $observer + * @return void + */ + public function forceAdminPasswordChange($observer) + { + if (!$this->isPasswordChangeForced()) { + return; + } + $session = $this->authSession; + if (!$session->isLoggedIn()) { + return; + } + $actionList = [ + 'adminhtml_system_account_index', + 'adminhtml_system_account_save', + 'adminhtml_auth_logout', + ]; + $controller = $observer->getEvent()->getControllerAction(); + /** @var \Magento\Framework\App\RequestInterface $request */ + $request = $observer->getEvent()->getRequest(); + if ($this->authSession->getPciAdminUserIsPasswordExpired()) { + if (!in_array($request->getFullActionName(), $actionList)) { + if ($this->authorization->isAllowed('Magento_Backend::myaccount')) { + $controller->getResponse()->setRedirect($this->url->getUrl('adminhtml/system_account/')); + $this->actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_POST_DISPATCH, true); + } else { + /* + * if admin password is expired and access to 'My Account' page is denied + * than we need to do force logout with error message + */ + $this->authSession->clearStorage(); + $this->session->clearStorage(); + $this->messageManager->addError( + __('Your password has expired; please contact your administrator.') + ); + $controller->getRequest()->setDispatched(false); + } + } + } + } + + /** + * Check whether password change is forced + * + * @return bool + */ + public function isPasswordChangeForced() + { + return (bool)(int)$this->backendConfig->getValue('admin/security/password_is_forced'); + } +} diff --git a/app/code/Magento/User/Model/Resource/User.php b/app/code/Magento/User/Model/Resource/User.php index 5d8d64b7706de..9f2bf7f3d3659 100644 --- a/app/code/Magento/User/Model/Resource/User.php +++ b/app/code/Magento/User/Model/Resource/User.php @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\User\Model\Resource; use Magento\Authorization\Model\Acl\Role\Group as RoleGroup; @@ -149,7 +147,9 @@ public function hasAssigned2Role($user) $connection = $this->getConnection(); $select = $connection->select(); - $select->from($this->getTable('authorization_role'))->where('parent_id > :parent_id')->where('user_id = :user_id'); + $select->from($this->getTable('authorization_role')) + ->where('parent_id > :parent_id') + ->where('user_id = :user_id'); $binds = ['parent_id' => 0, 'user_id' => $userId]; @@ -476,4 +476,151 @@ public function updateRoleUsersAcl(\Magento\Authorization\Model\Role $role) return $rowsCount > 0; } + + /** + * Unlock specified user record(s) + * + * @param int|int[] $userIds + * @return int number of affected rows + */ + public function unlock($userIds) + { + if (!is_array($userIds)) { + $userIds = [$userIds]; + } + return $this->getConnection()->update( + $this->getMainTable(), + ['failures_num' => 0, 'first_failure' => null, 'lock_expires' => null], + $this->getIdFieldName() . ' IN (' . $this->getConnection()->quote($userIds) . ')' + ); + } + + /** + * Lock specified user record(s) + * + * @param int|int[] $userIds + * @param int $exceptId + * @param int $lifetime + * @return int number of affected rows + */ + public function lock($userIds, $exceptId, $lifetime) + { + if (!is_array($userIds)) { + $userIds = [$userIds]; + } + $exceptId = (int)$exceptId; + return $this->getConnection()->update( + $this->getMainTable(), + ['lock_expires' => $this->dateTime->formatDate(time() + $lifetime)], + "{$this->getIdFieldName()} IN (" . $this->getConnection()->quote( + $userIds + ) . ")\n AND {$this->getIdFieldName()} <> {$exceptId}" + ); + } + + /** + * Increment failures count along with updating lock expire and first failure dates + * + * @param ModelUser $user + * @param int|false $setLockExpires + * @param int|false $setFirstFailure + * @return void + */ + public function updateFailure($user, $setLockExpires = false, $setFirstFailure = false) + { + $update = ['failures_num' => new \Zend_Db_Expr('failures_num + 1')]; + if (false !== $setFirstFailure) { + $update['first_failure'] = $this->dateTime->formatDate($setFirstFailure); + $update['failures_num'] = 1; + } + if (false !== $setLockExpires) { + $update['lock_expires'] = $this->dateTime->formatDate($setLockExpires); + } + $this->getConnection()->update( + $this->getMainTable(), + $update, + $this->getConnection()->quoteInto("{$this->getIdFieldName()} = ?", $user->getId()) + ); + } + + /** + * Purge and get remaining old password hashes + * + * @param ModelUser $user + * @param int $retainLimit + * @return array + */ + public function getOldPasswords($user, $retainLimit = 4) + { + $userId = (int)$user->getId(); + $table = $this->getTable('admin_passwords'); + + // purge expired passwords, except that should retain + $retainPasswordIds = $this->getConnection()->fetchCol( + $this->getConnection() + ->select() + ->from($table, 'password_id') + ->where('user_id = :user_id') + ->order('expires ' . \Magento\Framework\DB\Select::SQL_DESC) + ->order('password_id ' . \Magento\Framework\DB\Select::SQL_DESC) + ->limit($retainLimit), + [':user_id' => $userId] + ); + $where = ['user_id = ?' => $userId, 'expires <= ?' => time()]; + if ($retainPasswordIds) { + $where['password_id NOT IN (?)'] = $retainPasswordIds; + } + $this->getConnection()->delete($table, $where); + + // now get all remained passwords + return $this->getConnection()->fetchCol( + $this->getConnection() + ->select() + ->from($table, 'password_hash') + ->where('user_id = :user_id'), + [':user_id' => $userId] + ); + } + + /** + * Remember a password hash for further usage + * + * @param ModelUser $user + * @param string $passwordHash + * @param int $lifetime + * @return void + */ + public function trackPassword($user, $passwordHash, $lifetime) + { + $now = time(); + $this->getConnection()->insert( + $this->getTable('admin_passwords'), + [ + 'user_id' => $user->getId(), + 'password_hash' => $passwordHash, + 'expires' => $now + $lifetime, + 'last_updated' => $now + ] + ); + } + + /** + * Get latest password for specified user id + * Possible false positive when password was changed several times with different lifetime configuration + * + * @param int $userId + * @return array + */ + public function getLatestPassword($userId) + { + return $this->getConnection()->fetchRow( + $this->getConnection() + ->select() + ->from($this->getTable('admin_passwords')) + ->where('user_id = :user_id') + ->order('password_id ' . \Magento\Framework\DB\Select::SQL_DESC) + ->limit(1), + [':user_id' => $userId] + ); + } } diff --git a/app/code/Magento/User/Model/System/Config/Source/Password.php b/app/code/Magento/User/Model/System/Config/Source/Password.php new file mode 100644 index 0000000000000..a444a91780318 --- /dev/null +++ b/app/code/Magento/User/Model/System/Config/Source/Password.php @@ -0,0 +1,25 @@ + 0, 'label' => __('Recommended')], ['value' => 1, 'label' => __('Forced')]]; + } +} diff --git a/app/code/Magento/User/README.md b/app/code/Magento/User/README.md index 7795016d23674..cbefec77d1258 100644 --- a/app/code/Magento/User/README.md +++ b/app/code/Magento/User/README.md @@ -3,3 +3,4 @@ **User** enables admin users to manage and assign roles to administrators and other non-customer users, reset user passwords, and invalidate access tokens. Different roles can be assigned to different users to define their permissions. +For admin passwords, it enables setting lifetimes and locking them when expired or when a specified numbers of failures have occurred. It allows preventing password brute force attacks for system backend. diff --git a/app/code/Magento/User/Setup/UpgradeSchema.php b/app/code/Magento/User/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000..27aec4b0283ea --- /dev/null +++ b/app/code/Magento/User/Setup/UpgradeSchema.php @@ -0,0 +1,106 @@ +startSetup(); + $tableAdmins = $installer->getTable('admin_user'); + + $installer->getConnection()->addColumn( + $tableAdmins, + 'failures_num', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 'nullable' => true, + 'default' => 0, + 'comment' => 'Failure Number' + ] + ); + $installer->getConnection()->addColumn( + $tableAdmins, + 'first_failure', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + 'comment' => 'First Failure' + ] + ); + $installer->getConnection()->addColumn( + $tableAdmins, + 'lock_expires', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + 'comment' => 'Expiration Lock Dates' + ] + ); + + /** + * Create table 'admin_passwords' + */ + $table = $installer->getConnection()->newTable( + $installer->getTable('admin_passwords') + )->addColumn( + 'password_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Password Id' + )->addColumn( + 'user_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'User Id' + )->addColumn( + 'password_hash', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 100, + [], + 'Password Hash' + )->addColumn( + 'expires', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Expires' + )->addColumn( + 'last_updated', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Last Updated' + )->addIndex( + $installer->getIdxName('admin_passwords', ['user_id']), + ['user_id'] + )->addForeignKey( + $installer->getFkName('admin_passwords', 'user_id', 'admin_user', 'user_id'), + 'user_id', + $installer->getTable('admin_user'), + 'user_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + )->setComment( + 'Admin Passwords' + ); + $installer->getConnection()->createTable($table); + + $installer->endSetup(); + } +} diff --git a/app/code/Magento/User/Test/Unit/Console/UnlockAdminAccountCommandTest.php b/app/code/Magento/User/Test/Unit/Console/UnlockAdminAccountCommandTest.php new file mode 100644 index 0000000000000..bf1a7cf8806fa --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Console/UnlockAdminAccountCommandTest.php @@ -0,0 +1,48 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + parent::setUp(); + } + + /** + * Test configure() method implicitly via construct invocation. + * + * @return void + */ + public function testConfigure() + { + $this->command = $this->objectManager + ->getObject('Magento\User\Console\UnlockAdminAccountCommand'); + + $this->assertEquals(UnlockAdminAccountCommand::COMMAND_ADMIN_ACCOUNT_UNLOCK, $this->command->getName()); + $this->assertEquals(UnlockAdminAccountCommand::COMMAND_DESCRIPTION, $this->command->getDescription()); + $this->command->getDefinition()->getArgument(UnlockAdminAccountCommand::ARGUMENT_ADMIN_USERNAME); + $this->assertContains('This command unlocks an admin account by its username', $this->command->getHelp()); + } +} diff --git a/app/code/Magento/User/Test/Unit/Helper/DataTest.php b/app/code/Magento/User/Test/Unit/Helper/DataTest.php new file mode 100644 index 0000000000000..28f9fec77f164 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Helper/DataTest.php @@ -0,0 +1,67 @@ +mathRandomMock = $this->getMockBuilder('Magento\Framework\Math\Random') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->configMock = $this->getMockBuilder('Magento\Backend\App\ConfigInterface') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + 'Magento\User\Helper\Data', + [ + 'config' => $this->configMock, + 'mathRandom' => $this->mathRandomMock + ] + ); + } + + public function testGenerateResetPasswordLinkToken() + { + $hash = 'hashString'; + $this->mathRandomMock->expects($this->once())->method('getUniqueHash')->willReturn($hash); + $this->assertEquals($hash, $this->model->generateResetPasswordLinkToken()); + } + + public function testGetResetPasswordLinkExpirationPeriod() + { + $value = '123'; + $this->configMock->expects($this->once()) + ->method('getValue') + ->with(\Magento\User\Helper\Data::XML_PATH_ADMIN_RESET_PASSWORD_LINK_EXPIRATION_PERIOD) + ->willReturn($value); + $this->assertEquals((int) $value, $this->model->getResetPasswordLinkExpirationPeriod()); + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/Plugin/AuthorizationRoleTest.php b/app/code/Magento/User/Test/Unit/Model/Plugin/AuthorizationRoleTest.php new file mode 100644 index 0000000000000..8a486e4a745b7 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Model/Plugin/AuthorizationRoleTest.php @@ -0,0 +1,55 @@ +userResourceModelMock = $this->getMockBuilder('Magento\User\Model\Resource\User') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->roleMock = $this->getMockBuilder('Magento\Authorization\Model\Role') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + 'Magento\User\Model\Plugin\AuthorizationRole', + [ + 'userResourceModel' => $this->userResourceModelMock + ] + ); + } + + public function testAfterSave() + { + $this->userResourceModelMock->expects($this->once())->method('updateRoleUsersAcl')->with($this->roleMock); + $this->assertInstanceOf( + '\Magento\Authorization\Model\Role', + $this->model->afterSave($this->roleMock, $this->roleMock) + ); + } +} diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index 05e2f75ad8be1..1f37bd9a15524 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -16,59 +16,71 @@ class UserTest extends \PHPUnit_Framework_TestCase { /** @var \Magento\User\Model\User */ - protected $_model; + protected $model; - /** @var \Magento\User\Helper\Data */ - protected $_userData; + /** @var \Magento\User\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ + protected $userDataMock; /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $_transportBuilderMock; + protected $transportBuilderMock; /** @var \Magento\Framework\Model\Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $_contextMock; + protected $contextMock; /** @var \Magento\User\Model\Resource\User|\PHPUnit_Framework_MockObject_MockObject */ - protected $_resourceMock; + protected $resourceMock; /** @var \Magento\Framework\Data\Collection\AbstractDb|\PHPUnit_Framework_MockObject_MockObject */ - protected $_collectionMock; + protected $collectionMock; /** @var \Magento\Framework\Mail\TransportInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_transportMock; + protected $transportMock; /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_storeManagerMock; + protected $storeManagerMock; /** @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject */ - protected $_storetMock; + protected $storetMock; - /** @var \Magento\Backend\App\ConfigInterface */ - protected $_configMock; + /** @var \Magento\Backend\App\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $configMock; /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_encryptorMock; + protected $encryptorMock; + + /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $eventManagerMock; + + /** @var \Magento\Framework\Validator\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $validatorObjectFactoryMock; + + /** @var \Magento\User\Model\UserValidationRules|\PHPUnit_Framework_MockObject_MockObject */ + protected $validationRulesMock; + + /** @var \Magento\Authorization\Model\RoleFactory|\PHPUnit_Framework_MockObject_MockObject */ + protected $roleFactoryMock; /** * Set required values */ protected function setUp() { - $this->_userData = $this->getMockBuilder( + $this->userDataMock = $this->getMockBuilder( 'Magento\User\Helper\Data' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_contextMock = $this->getMockBuilder( + $this->contextMock = $this->getMockBuilder( 'Magento\Framework\Model\Context' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_resourceMock = $this->getMockBuilder( + $this->resourceMock = $this->getMockBuilder( 'Magento\User\Model\Resource\User' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_collectionMock = $this->getMockBuilder( + $this->collectionMock = $this->getMockBuilder( 'Magento\Framework\Data\Collection\AbstractDb' )->disableOriginalConstructor()->setMethods( [] @@ -78,69 +90,74 @@ protected function setUp() )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $eventManagerMock = $this->getMockBuilder( + $this->eventManagerMock = $this->getMockBuilder( 'Magento\Framework\Event\ManagerInterface' )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - $objectFactoryMock = $this->getMockBuilder( + ['dispatch'] + )->getMockForAbstractClass(); + $this->validatorObjectFactoryMock = $this->getMockBuilder( 'Magento\Framework\Validator\DataObjectFactory' )->disableOriginalConstructor()->setMethods( ['create'] )->getMock(); - $roleFactoryMock = $this->getMockBuilder( + $this->roleFactoryMock = $this->getMockBuilder( 'Magento\Authorization\Model\RoleFactory' )->disableOriginalConstructor()->setMethods( ['create'] )->getMock(); - $this->_transportMock = $this->getMockBuilder( + $this->transportMock = $this->getMockBuilder( 'Magento\Framework\Mail\TransportInterface' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_transportBuilderMock = $this->getMockBuilder( - '\Magento\Framework\Mail\Template\TransportBuilder' + $this->transportBuilderMock = $this->getMockBuilder( + 'Magento\Framework\Mail\Template\TransportBuilder' + )->disableOriginalConstructor()->setMethods( + [] + )->getMock(); + $this->storetMock = $this->getMockBuilder( + 'Magento\Store\Model\Store' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_storetMock = $this->getMockBuilder( - '\Magento\Store\Model\Store' + $this->storeManagerMock = $this->getMockBuilder( + 'Magento\Store\Model\StoreManagerInterface' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_storeManagerMock = $this->getMockBuilder( - '\Magento\Store\Model\StoreManagerInterface' + + $this->configMock = $this->getMockBuilder( + 'Magento\Backend\App\ConfigInterface' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_configMock = $this->getMockBuilder( - '\Magento\Backend\App\ConfigInterface' + $this->validationRulesMock = $this->getMockBuilder( + 'Magento\User\Model\UserValidationRules' )->disableOriginalConstructor()->setMethods( [] )->getMock(); - $this->_encryptorMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface') + $this->encryptorMock = $this->getMockBuilder('Magento\Framework\Encryption\EncryptorInterface') ->setMethods(['validateHash']) ->getMockForAbstractClass(); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_model = $helper->getObject( + $this->model = $helper->getObject( 'Magento\User\Model\User', [ - 'eventManager' => $eventManagerMock, - 'userData' => $this->_userData, - 'context' => $this->_contextMock, + 'eventManager' => $this->eventManagerMock, + 'userData' => $this->userDataMock, 'registry' => $coreRegistry, - 'resource' => $this->_resourceMock, - 'resourceCollection' => $this->_collectionMock, - 'validatorObjectFactory' => $objectFactoryMock, - 'roleFactory' => $roleFactoryMock, - 'transportBuilder' => $this->_transportBuilderMock, - 'storeManager' => $this->_storeManagerMock, - 'validationRules' => new UserValidationRules(), - 'config' => $this->_configMock, - 'encryptor' => $this->_encryptorMock + 'resource' => $this->resourceMock, + 'resourceCollection' => $this->collectionMock, + 'validatorObjectFactory' => $this->validatorObjectFactoryMock, + 'roleFactory' => $this->roleFactoryMock, + 'transportBuilder' => $this->transportBuilderMock, + 'storeManager' => $this->storeManagerMock, + 'validationRules' => $this->validationRulesMock, + 'config' => $this->configMock, + 'encryptor' => $this->encryptorMock ] ); } @@ -152,11 +169,11 @@ public function testSendPasswordResetNotificationEmail() $firstName = 'Foo'; $lastName = 'Bar'; - $this->_model->setEmail($email); - $this->_model->setFirstname($firstName); - $this->_model->setLastname($lastName); + $this->model->setEmail($email); + $this->model->setFirstname($firstName); + $this->model->setLastname($lastName); - $this->_configMock->expects( + $this->configMock->expects( $this->at(0) )->method( 'getValue' @@ -165,7 +182,7 @@ public function testSendPasswordResetNotificationEmail() )->will( $this->returnValue('templateId') ); - $this->_configMock->expects( + $this->configMock->expects( $this->at(1) )->method( 'getValue' @@ -174,17 +191,17 @@ public function testSendPasswordResetNotificationEmail() )->will( $this->returnValue('sender') ); - $this->_transportBuilderMock->expects($this->once())->method('setTemplateOptions')->will($this->returnSelf()); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects($this->once())->method('setTemplateOptions')->will($this->returnSelf()); + $this->transportBuilderMock->expects( $this->once() )->method( 'setTemplateVars' )->with( - ['user' => $this->_model, 'store' => $this->_storetMock] + ['user' => $this->model, 'store' => $this->storetMock] )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'addTo' @@ -194,7 +211,7 @@ public function testSendPasswordResetNotificationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'setFrom' @@ -203,7 +220,7 @@ public function testSendPasswordResetNotificationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'setTemplateIdentifier' @@ -212,26 +229,26 @@ public function testSendPasswordResetNotificationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'getTransport' )->will( - $this->returnValue($this->_transportMock) + $this->returnValue($this->transportMock) ); - $this->_transportMock->expects($this->once())->method('sendMessage'); + $this->transportMock->expects($this->once())->method('sendMessage'); - $this->_storeManagerMock->expects( + $this->storeManagerMock->expects( $this->once() )->method( 'getStore' )->with( $storeId )->will( - $this->returnValue($this->_storetMock) + $this->returnValue($this->storetMock) ); - $this->assertInstanceOf('\Magento\User\Model\User', $this->_model->sendPasswordResetNotificationEmail()); + $this->assertInstanceOf('\Magento\User\Model\User', $this->model->sendPasswordResetNotificationEmail()); } public function testSendPasswordResetConfirmationEmail() @@ -241,11 +258,11 @@ public function testSendPasswordResetConfirmationEmail() $firstName = 'Foo'; $lastName = 'Bar'; - $this->_model->setEmail($email); - $this->_model->setFirstname($firstName); - $this->_model->setLastname($lastName); + $this->model->setEmail($email); + $this->model->setFirstname($firstName); + $this->model->setLastname($lastName); - $this->_configMock->expects( + $this->configMock->expects( $this->at(0) )->method( 'getValue' @@ -254,7 +271,7 @@ public function testSendPasswordResetConfirmationEmail() )->will( $this->returnValue('templateId') ); - $this->_configMock->expects( + $this->configMock->expects( $this->at(1) )->method( 'getValue' @@ -263,17 +280,17 @@ public function testSendPasswordResetConfirmationEmail() )->will( $this->returnValue('sender') ); - $this->_transportBuilderMock->expects($this->once())->method('setTemplateOptions')->will($this->returnSelf()); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects($this->once())->method('setTemplateOptions')->will($this->returnSelf()); + $this->transportBuilderMock->expects( $this->once() )->method( 'setTemplateVars' )->with( - ['user' => $this->_model, 'store' => $this->_storetMock] + ['user' => $this->model, 'store' => $this->storetMock] )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'addTo' @@ -283,7 +300,7 @@ public function testSendPasswordResetConfirmationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'setFrom' @@ -292,7 +309,7 @@ public function testSendPasswordResetConfirmationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'setTemplateIdentifier' @@ -301,40 +318,40 @@ public function testSendPasswordResetConfirmationEmail() )->will( $this->returnSelf() ); - $this->_transportBuilderMock->expects( + $this->transportBuilderMock->expects( $this->once() )->method( 'getTransport' )->will( - $this->returnValue($this->_transportMock) + $this->returnValue($this->transportMock) ); - $this->_transportMock->expects($this->once())->method('sendMessage'); + $this->transportMock->expects($this->once())->method('sendMessage'); - $this->_storeManagerMock->expects( + $this->storeManagerMock->expects( $this->once() )->method( 'getStore' )->with( $storeId )->will( - $this->returnValue($this->_storetMock) + $this->returnValue($this->storetMock) ); - $this->assertInstanceOf('\Magento\User\Model\User', $this->_model->sendPasswordResetConfirmationEmail()); + $this->assertInstanceOf('\Magento\User\Model\User', $this->model->sendPasswordResetConfirmationEmail()); } public function testVerifyIdentity() { $password = 'password'; - $this->_encryptorMock + $this->encryptorMock ->expects($this->once()) ->method('validateHash') - ->with($password, $this->_model->getPassword()) + ->with($password, $this->model->getPassword()) ->will($this->returnValue(true)); - $this->_model->setIsActive(true); - $this->_resourceMock->expects($this->once())->method('hasAssigned2Role')->will($this->returnValue(true)); + $this->model->setIsActive(true); + $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->will($this->returnValue(true)); $this->assertTrue( - $this->_model->verifyIdentity($password), + $this->model->verifyIdentity($password), 'Identity verification failed while should have passed.' ); } @@ -342,13 +359,13 @@ public function testVerifyIdentity() public function testVerifyIdentityFailure() { $password = 'password'; - $this->_encryptorMock + $this->encryptorMock ->expects($this->once()) ->method('validateHash') - ->with($password, $this->_model->getPassword()) + ->with($password, $this->model->getPassword()) ->will($this->returnValue(false)); $this->assertFalse( - $this->_model->verifyIdentity($password), + $this->model->verifyIdentity($password), 'Identity verification passed while should have failed.' ); } @@ -356,33 +373,253 @@ public function testVerifyIdentityFailure() public function testVerifyIdentityInactiveRecord() { $password = 'password'; - $this->_encryptorMock + $this->encryptorMock ->expects($this->once()) ->method('validateHash') - ->with($password, $this->_model->getPassword()) + ->with($password, $this->model->getPassword()) ->will($this->returnValue(true)); - $this->_model->setIsActive(false); + $this->model->setIsActive(false); $this->setExpectedException( 'Magento\\Framework\\Exception\\AuthenticationException', 'You did not sign in correctly or your account is temporarily disabled.' ); - $this->_model->verifyIdentity($password); + $this->model->verifyIdentity($password); } public function testVerifyIdentityNoAssignedRoles() { $password = 'password'; - $this->_encryptorMock + $this->encryptorMock ->expects($this->once()) ->method('validateHash') - ->with($password, $this->_model->getPassword()) + ->with($password, $this->model->getPassword()) ->will($this->returnValue(true)); - $this->_model->setIsActive(true); - $this->_resourceMock->expects($this->once())->method('hasAssigned2Role')->will($this->returnValue(false)); + $this->model->setIsActive(true); + $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->will($this->returnValue(false)); $this->setExpectedException( 'Magento\\Framework\\Exception\\AuthenticationException', 'You need more permissions to access this.' ); - $this->_model->verifyIdentity($password); + $this->model->verifyIdentity($password); + } + + public function testSleep() + { + $excludedProperties = [ + '_eventManager', + '_cacheManager', + '_registry', + '_appState', + '_userData', + '_config', + '_validatorObject', + '_roleFactory', + '_encryptor', + '_transportBuilder', + '_storeManager', + '_validatorBeforeSave' + ]; + $actualResult = $this->model->__sleep(); + $this->assertNotEmpty($actualResult); + $expectedResult = array_intersect($actualResult, $excludedProperties); + $this->assertEmpty($expectedResult); + } + + public function testBeforeSave() + { + $this->eventManagerMock->expects($this->any())->method('dispatch'); + $this->model->setIsActive(1); + $actualData = $this->model->beforeSave()->getData(); + $this->assertArrayHasKey('modified', $actualData); + $this->assertArrayHasKey('extra', $actualData); + $this->assertArrayHasKey('password', $actualData); + $this->assertArrayHasKey('is_active', $actualData); + } + + public function testValidateOk() + { + /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + $validatorMock = $this->getMockBuilder('Magento\Framework\Validator\DataObject') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); + $this->validationRulesMock->expects($this->once()) + ->method('addUserInfoRules') + ->with($validatorMock); + $validatorMock->expects($this->once())->method('isValid')->willReturn(true); + $this->assertTrue($this->model->validate()); + } + + public function testValidateInvalid() + { + $messages = ['Invalid username']; + /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ + $validatorMock = $this->getMockBuilder('Magento\Framework\Validator\DataObject') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); + $this->validationRulesMock->expects($this->once()) + ->method('addUserInfoRules') + ->with($validatorMock); + $validatorMock->expects($this->once())->method('isValid')->willReturn(false); + $validatorMock->expects($this->once())->method('getMessages')->willReturn($messages); + $this->assertEquals($messages, $this->model->validate()); + } + + public function testSaveExtra() + { + $data = [1, 2, 3]; + $this->resourceMock->expects($this->once())->method('saveExtra')->with($this->model, serialize($data)); + $this->assertInstanceOf('Magento\User\Model\User', $this->model->saveExtra($data)); + } + + public function testGetRoles() + { + $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn([]); + $this->assertInternalType('array', $this->model->getRoles()); + } + + public function testGetRole() + { + $roles = ['role']; + $roleMock = $this->getMockBuilder('Magento\Authorization\Model\Role') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); + $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); + $roleMock->expects($this->once())->method('load')->with($roles[0]); + $this->assertInstanceOf('Magento\Authorization\Model\Role', $this->model->getRole()); + } + + public function testDeleteFromRole() + { + $this->resourceMock->expects($this->once())->method('deleteFromRole')->with($this->model); + $this->assertInstanceOf('Magento\User\Model\User', $this->model->deleteFromRole()); + } + + public function testRoleUserExistsTrue() + { + $result = ['role']; + $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); + $this->assertTrue($this->model->roleUserExists()); + } + + public function testRoleUserExistsFalse() + { + $result = []; + $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); + $this->assertFalse($this->model->roleUserExists()); + } + + public function testGetAclRole() + { + $roles = ['role']; + $result = 1; + $roleMock = $this->getMockBuilder('Magento\Authorization\Model\Role') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); + $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); + $roleMock->expects($this->once())->method('load')->with($roles[0]); + $roleMock->expects($this->once())->method('getId')->willReturn($result); + $this->assertEquals($result, $this->model->getAclRole()); + } + + /** + * @dataProvider authenticateDataProvider + */ + public function testAuthenticate($usernameIn, $usernameOut, $expectedResult) + { + $password = 'password'; + $config = 'config'; + + $data = ['id' => 1, 'is_active' => 1, 'username' => $usernameOut]; + + $this->configMock->expects($this->once()) + ->method('isSetFlag') + ->with('admin/security/use_case_sensitive_login') + ->willReturn($config); + $this->eventManagerMock->expects($this->any())->method('dispatch'); + + $this->resourceMock->expects($this->any())->method('loadByUsername')->willReturn($data); + $this->model->setIdFieldName('id'); + + $this->encryptorMock->expects($this->any())->method('validateHash')->willReturn(true); + $this->resourceMock->expects($this->any())->method('hasAssigned2Role')->willReturn(true); + $this->assertEquals($expectedResult, $this->model->authenticate($usernameIn, $password)); + } + + public function authenticateDataProvider() + { + return [ + 'success' => [ + 'usernameIn' => 'username', + 'usernameOut' => 'username', + 'expectedResult' => true + ], + 'failedUsername' => [ + 'usernameIn' => 'username1', + 'usernameOut' => 'username2', + 'expectedResult' => false + ] + ]; + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testAuthenticateException() + { + $username = 'username'; + $password = 'password'; + $config = 'config'; + + $this->configMock->expects($this->once()) + ->method('isSetFlag') + ->with('admin/security/use_case_sensitive_login') + ->willReturn($config); + + $this->eventManagerMock->expects($this->any())->method('dispatch'); + $this->resourceMock->expects($this->once()) + ->method('loadByUsername') + ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__())); + $this->model->authenticate($username, $password); + } + + public function testChangeResetPasswordLinkToken() + { + $token = '1'; + $this->assertInstanceOf('Magento\User\Model\User', $this->model->changeResetPasswordLinkToken($token)); + $this->assertEquals($token, $this->model->getRpToken()); + $this->assertInternalType('string', $this->model->getRpTokenCreatedAt()); + } + + public function testIsResetPasswordLinkTokenExpiredEmptyToken() + { + $this->assertTrue($this->model->isResetPasswordLinkTokenExpired()); + } + + public function testIsResetPasswordLinkTokenExpiredIsExpiredToken() + { + $this->model->setRpToken('1'); + $this->model->setRpTokenCreatedAt( + (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) + ); + $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(0); + $this->assertTrue($this->model->isResetPasswordLinkTokenExpired()); + } + + public function testIsResetPasswordLinkTokenExpiredIsNotExpiredToken() + { + $this->model->setRpToken('1'); + $this->model->setRpTokenCreatedAt( + (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) + ); + $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(1); + $this->assertFalse($this->model->isResetPasswordLinkTokenExpired()); } } diff --git a/app/code/Magento/User/etc/acl.xml b/app/code/Magento/User/etc/acl.xml index 502b706e13856..261a998abc757 100644 --- a/app/code/Magento/User/etc/acl.xml +++ b/app/code/Magento/User/etc/acl.xml @@ -12,6 +12,7 @@ + diff --git a/app/code/Magento/User/etc/adminhtml/di.xml b/app/code/Magento/User/etc/adminhtml/di.xml index 26f2ed8ba4485..ad550cbf5058d 100644 --- a/app/code/Magento/User/etc/adminhtml/di.xml +++ b/app/code/Magento/User/etc/adminhtml/di.xml @@ -7,4 +7,9 @@ --> + + + Magento\Framework\Authorization + + diff --git a/app/code/Magento/User/etc/adminhtml/events.xml b/app/code/Magento/User/etc/adminhtml/events.xml new file mode 100644 index 0000000000000..01f8142a549d0 --- /dev/null +++ b/app/code/Magento/User/etc/adminhtml/events.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/User/etc/adminhtml/menu.xml b/app/code/Magento/User/etc/adminhtml/menu.xml index bac4265e50aba..9e9d9ded06c0c 100644 --- a/app/code/Magento/User/etc/adminhtml/menu.xml +++ b/app/code/Magento/User/etc/adminhtml/menu.xml @@ -10,5 +10,6 @@ + diff --git a/app/code/Magento/User/etc/adminhtml/system.xml b/app/code/Magento/User/etc/adminhtml/system.xml index c49f8f81f1ddf..8fecae6a8f95b 100644 --- a/app/code/Magento/User/etc/adminhtml/system.xml +++ b/app/code/Magento/User/etc/adminhtml/system.xml @@ -15,6 +15,23 @@ Magento\Config\Model\Config\Source\Email\Template + + + + We will disable this feature if the value is empty. + + + + + + + We will disable this feature if the value is empty. + + + + Magento\User\Model\System\Config\Source\Password + + diff --git a/app/code/Magento/User/etc/config.xml b/app/code/Magento/User/etc/config.xml index 6ef863af41a76..d8736de1b56ec 100644 --- a/app/code/Magento/User/etc/config.xml +++ b/app/code/Magento/User/etc/config.xml @@ -14,6 +14,12 @@ 1 admin_emails_reset_password_template + + 6 + 30 + 90 + 1 + diff --git a/app/code/Magento/User/etc/di.xml b/app/code/Magento/User/etc/di.xml index 962ccc2aa632a..1973befe228e6 100644 --- a/app/code/Magento/User/etc/di.xml +++ b/app/code/Magento/User/etc/di.xml @@ -15,4 +15,11 @@ + + + + Magento\User\Console\UnlockAdminAccountCommand + + + diff --git a/app/code/Magento/User/i18n/de_DE.csv b/app/code/Magento/User/i18n/de_DE.csv index 3f27379d04352..63367a6ff3a32 100644 --- a/app/code/Magento/User/i18n/de_DE.csv +++ b/app/code/Magento/User/i18n/de_DE.csv @@ -95,3 +95,26 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,Benutzername +"Locked Users","Gesperrte Benutzer" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","Das Konto ist gesperrt." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Empfohlen +Forced,Dechiffriert +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Admin Accounts Locks","Admin Kontosperrungen" +Unlock,Entsperren +"Last login","Letzter Login" +Failures,Fehler +Unlocked,"Gesperrt bis" diff --git a/app/code/Magento/User/i18n/en_US.csv b/app/code/Magento/User/i18n/en_US.csv index 3f27379d04352..1f56f067006fc 100644 --- a/app/code/Magento/User/i18n/en_US.csv +++ b/app/code/Magento/User/i18n/en_US.csv @@ -95,3 +95,26 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,Username +"Locked Users","Locked Users" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","This account is locked." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Recommended +Forced,Forced +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Admin Accounts Locks","Admin Accounts Locks" +Unlock,Unlock +"Last login","Last login" +Failures,Failures +Unlocked,Unlocked diff --git a/app/code/Magento/User/i18n/es_ES.csv b/app/code/Magento/User/i18n/es_ES.csv index 3f27379d04352..4dd4f371b4af5 100644 --- a/app/code/Magento/User/i18n/es_ES.csv +++ b/app/code/Magento/User/i18n/es_ES.csv @@ -95,3 +95,27 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,"Nombre de Usuario" +"Locked Users","Usuarios bloqueados" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","Esta cuenta está bloqueada." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Recomendado +Forced,Forzado +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Encryption Key Change","Cambio de clave de encriptación" +"Admin Accounts Locks","Bloqueo de cuentas de administración" +Unlock,Desbloquear +"Last login","Ultimo acceso" +Failures,Fallos +Unlocked,"Bloqueado hasta" diff --git a/app/code/Magento/User/i18n/fr_FR.csv b/app/code/Magento/User/i18n/fr_FR.csv index 3f27379d04352..bbe1e63a96cbd 100644 --- a/app/code/Magento/User/i18n/fr_FR.csv +++ b/app/code/Magento/User/i18n/fr_FR.csv @@ -95,3 +95,27 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,"Nom d'utilisateur" +"Locked Users","Utilisateurs verrouillés" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","Le compte est verrouillé." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Recommandé +Forced,Forcé +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Encryption Key Change","Modification Clé de Chiffrement" +"Admin Accounts Locks","Verrous Comptes Administrateurs" +Unlock,Déverrouiller +"Last login","Dernière connexion" +Failures,Echecs +Unlocked,"Verrouillé jusqu'" diff --git a/app/code/Magento/User/i18n/nl_NL.csv b/app/code/Magento/User/i18n/nl_NL.csv index 3f27379d04352..91813e3a13311 100644 --- a/app/code/Magento/User/i18n/nl_NL.csv +++ b/app/code/Magento/User/i18n/nl_NL.csv @@ -95,3 +95,27 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,Gebruikersnaam +"Locked Users","Gesloten Gebruikers" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","Dit account is geblokkeerd." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Aanbevolen +Forced,Gedwongen +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Encryption Key Change","Encryptie Code Veranderen" +"Admin Accounts Locks","Beheerder Accounts Sloten" +Unlock,Openen +"Last login","Laatste login" +Failures,Mislukkingen +Unlocked,"Gesloten tot" diff --git a/app/code/Magento/User/i18n/pt_BR.csv b/app/code/Magento/User/i18n/pt_BR.csv index 3f27379d04352..d8d16b8cd04f7 100644 --- a/app/code/Magento/User/i18n/pt_BR.csv +++ b/app/code/Magento/User/i18n/pt_BR.csv @@ -95,3 +95,27 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,"Nome do usuário" +"Locked Users","Usuários Bloqueados" +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.","Esta conta está bloqueada." +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,Recomendado +Forced,Forçado +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Encryption Key Change","Mudança na Chave de Criptografia" +"Admin Accounts Locks","Bloqueios para Contas de Administrador" +Unlock,Desbloquear +"Last login","Último login" +Failures,Falhas +Unlocked,"Bloqueado até" diff --git a/app/code/Magento/User/i18n/zh_CN.csv b/app/code/Magento/User/i18n/zh_CN.csv index 3f27379d04352..1c10afb0b649b 100644 --- a/app/code/Magento/User/i18n/zh_CN.csv +++ b/app/code/Magento/User/i18n/zh_CN.csv @@ -95,3 +95,27 @@ Resources,Resources "Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?","Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?" "Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?","Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?" "Reset Password Template","Reset Password Template" +No,No +Yes,Yes +Username,用户名 +"Locked Users",锁定的用户 +"Unlocked %1 user(s).","Unlocked %1 user(s)." +"This account is locked.",该帐户已被锁定。 +"It's time to change your password.","It's time to change your password." +"It's time to change your password.","It's time to change your password." +"Sorry, but this password has already been used. Please create another.","Sorry, but this password has already been used. Please create another." +"Your password has expired; please contact your administrator.","Your password has expired; please contact your administrator." +Recommended,推荐 +Forced,强制 +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"We will disable this feature if the value is empty.","We will disable this feature if the value is empty." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Password Lifetime (days)","Password Lifetime (days)" +"We will disable this feature if the value is empty. ","We will disable this feature if the value is empty. " +"Password Change","Password Change" +"Encryption Key Change",加密密钥已更改 +"Admin Accounts Locks",管理帐户锁定 +Unlock,解锁 +"Last login",上一次登录 +Failures,失败 +Unlocked,锁定直到 diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml new file mode 100644 index 0000000000000..2098d0a7e7760 --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_block.xml @@ -0,0 +1,94 @@ + + + + + + + + lockedAdminsGrid + Magento\User\Model\Resource\User\Locked\Collection + user_id + 1 + + 1 + + + + + user_id + unlock + 1 + + + Unlock + */*/massUnlock + 1 + + + + + + + lockedAdminsGrid + + + + ID + number + 0 + user_id + user_id + col-id + col-id + + + + + Username + text + username + username + col-name + col-name + + + + + Last login + datetime + 0 + last_login + logdate + col-date + col-date + + + + + Failures + 0 + failures_num + failures_num + + + + + Unlocked + datetime + 0 + lock_expires + lock_expires + col-date + col-date + + + + + + + diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_grid.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_grid.xml new file mode 100644 index 0000000000000..bce6ee8a55c66 --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_grid.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_index.xml b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_index.xml new file mode 100644 index 0000000000000..ac9e34a19610d --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/layout/adminhtml_locks_index.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/app/code/Magento/Widget/Model/Template/Filter.php b/app/code/Magento/Widget/Model/Template/Filter.php index d2a9f11772deb..ea49425318740 100644 --- a/app/code/Magento/Widget/Model/Template/Filter.php +++ b/app/code/Magento/Widget/Model/Template/Filter.php @@ -34,6 +34,7 @@ class Filter extends \Magento\Cms\Model\Template\Filter * @param \Magento\Framework\App\State $appState * @param \Magento\Framework\UrlInterface $urlModel * @param \Pelago\Emogrifier $emogrifier + * @param \Magento\Email\Model\Source\Variables $configVariables * @param \Magento\Widget\Model\Resource\Widget $widgetResource * @param \Magento\Widget\Model\Widget $widget * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -51,6 +52,7 @@ public function __construct( \Magento\Framework\App\State $appState, \Magento\Framework\UrlInterface $urlModel, \Pelago\Emogrifier $emogrifier, + \Magento\Email\Model\Source\Variables $configVariables, \Magento\Widget\Model\Resource\Widget $widgetResource, \Magento\Widget\Model\Widget $widget ) { @@ -68,7 +70,8 @@ public function __construct( $layoutFactory, $appState, $urlModel, - $emogrifier + $emogrifier, + $configVariables ); } diff --git a/app/code/Magento/Widget/composer.json b/app/code/Magento/Widget/composer.json index 1a9190debb3a5..936d461a2a7f8 100644 --- a/app/code/Magento/Widget/composer.json +++ b/app/code/Magento/Widget/composer.json @@ -7,6 +7,7 @@ "magento/module-cms": "1.0.0-beta", "magento/module-backend": "1.0.0-beta", "magento/module-catalog": "1.0.0-beta", + "magento/module-email": "1.0.0-beta", "magento/module-theme": "1.0.0-beta", "magento/framework": "1.0.0-beta", "magento/module-variable": "1.0.0-beta", diff --git a/composer.json b/composer.json index a7cdb013bb38d..3d56ffc30ad81 100644 --- a/composer.json +++ b/composer.json @@ -111,6 +111,7 @@ "magento/module-downloadable": "self.version", "magento/module-eav": "self.version", "magento/module-email": "self.version", + "magento/module-encryption-key": "self.version", "magento/module-fedex": "self.version", "magento/module-gift-message": "self.version", "magento/module-google-adwords": "self.version", diff --git a/composer.lock b/composer.lock index 16141689d88af..878cafac4f2e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "20c37ec30731e0b8d0cd52a33c9dec89", + "hash": "3c94391144a4c2de663dfa465b3a2e8a", "packages": [ { "name": "braintree/braintree_php", diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Widget/Grid.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Widget/Grid.php index 41da1ae272f38..8e1e7be037137 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Widget/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Widget/Grid.php @@ -356,6 +356,10 @@ protected function getRow(array $filter, $isStrict = true) $rowTemplate = ($isStrict) ? $this->rowTemplateStrict : $this->rowTemplate; $rows = []; foreach ($filter as $value) { + if (strpos($value, '"') !== false) { + $rowTemplate = str_replace('"', '', $rowTemplate); + $value = $this->xpathEscape($value); + } $rows[] = sprintf($rowTemplate, $value); } $location = sprintf($this->rowPattern, implode(' and ', $rows)); @@ -453,4 +457,30 @@ public function openFirstRow() { $this->_rootElement->find($this->firstRowSelector, Locator::SELECTOR_XPATH)->click(); } + + /** + * Escape single and/or double quotes in XPath selector by concat() + * + * @param string $query + * @param string $defaultDelim [optional] + * @return string + */ + protected function xpathEscape($query, $defaultDelim = '"') + { + if (strpos($query, $defaultDelim) === false) { + return $defaultDelim . $query . $defaultDelim; + } + preg_match_all("#(?:('+)|[^']+)#", $query, $matches); + list($parts, $apos) = $matches; + $delim = ''; + foreach ($parts as $i => &$part) { + $delim = $apos[$i] ? '"' : "'"; + $part = $delim . $part . $delim; + } + if (count($parts) == 1) { + $parts[] = $delim . $delim; + } + + return 'concat(' . implode(',', $parts) . ')'; + } } diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationForm.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationForm.php index 875cf0fefabef..a73cfc3bd98fa 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationForm.php +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationForm.php @@ -13,5 +13,16 @@ */ class IntegrationForm extends FormTabs { - // + /** + * Get array of label => js error text. + * + * @param string $tabName + * @return array + */ + public function getJsErrors($tabName) + { + $tab = $this->getTab($tabName); + $this->openTab($tabName); + return $tab->getJsErrors(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationFormPageActions.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationFormPageActions.php index 6afacd6b0afab..48b34514e43d0 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationFormPageActions.php +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Block/Adminhtml/Integration/Edit/IntegrationFormPageActions.php @@ -15,14 +15,14 @@ class IntegrationFormPageActions extends FormPageActions { /** - * Save button + * Save button. * * @var string */ protected $saveNewButton = '[data-ui-id="integration-edit-content-save-split-button-button"]'; /** - * Click on "Save" with split button + * Click on "Save" with split button. * * @return void */ @@ -30,4 +30,35 @@ public function saveNew() { $this->_rootElement->find($this->saveNewButton)->click(); } + + /** + * Check if alert is present. + * + * @return bool + */ + public function isAlertPresent() + { + try { + $this->browser->getAlertText(); + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * Accept alert. + * + * @return void + */ + public function acceptAlert() + { + try { + while (true) { + $this->browser->acceptAlert(); + } + } catch (\Exception $e) { + return; + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertEmailValidationErrorGenerated.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertEmailValidationErrorGenerated.php new file mode 100644 index 0000000000000..e23e2a88a4e8e --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertEmailValidationErrorGenerated.php @@ -0,0 +1,52 @@ +getIntegrationForm()->getJsErrors("integration_info"); + $emailJsError = false; + foreach ($errors as $error) { + if (strpos($error, 'Please enter a valid email address') !== false) { + $emailJsError = true; + break; + } + } + \PHPUnit_Framework_Assert::assertTrue( + $emailJsError, + 'Failed to validate email address (' . $integration->getEmail() . ') when saving integration.' + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'Email address is properly validated when saving integration.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationNameDuplicationErrorMessage.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationNameDuplicationErrorMessage.php new file mode 100644 index 0000000000000..99613bf462381 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationNameDuplicationErrorMessage.php @@ -0,0 +1,51 @@ +getName()); + $actualMessage = $integrationIndexPage->getMessagesBlock()->getErrorMessages(); + \PHPUnit_Framework_Assert::assertEquals( + $expectedMessage, + $actualMessage, + 'Wrong error message is displayed.' + . "\nExpected: " . $expectedMessage + . "\nActual: " . $actualMessage + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'Duplicated integration name error message is correct.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationSuccessSaveMessageNotPresent.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationSuccessSaveMessageNotPresent.php new file mode 100644 index 0000000000000..ac23f60261388 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertIntegrationSuccessSaveMessageNotPresent.php @@ -0,0 +1,51 @@ +getMessagesBlock()->isVisible()) { + try { + $integrationIndex->getMessagesBlock()->getSuccessMessages(); + } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) { + $noSuccessMessage = true; + } + } else { + $noSuccessMessage = true; + } + \PHPUnit_Framework_Assert::assertTrue( + $noSuccessMessage, + 'Integration is not saved.' + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'Integration is not saved.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertNoAlertPopup.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertNoAlertPopup.php new file mode 100644 index 0000000000000..9dddeed18e9e1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertNoAlertPopup.php @@ -0,0 +1,45 @@ +getFormPageActions()->isAlertPresent(); + if ($isAlertPresent) { + $integrationNew->getFormPageActions()->acceptAlert(); + } + \PHPUnit_Framework_Assert::assertFalse( + $isAlertPresent, + 'Saving an integration should not cause alert.' + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'Integration is saved with no alert.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Fixture/Integration.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/Fixture/Integration.xml index f2ed1bfc1e24d..2712dcbae41d7 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/Fixture/Integration.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Fixture/Integration.xml @@ -14,7 +14,7 @@ repository_class="Magento\Integration\Test\Repository\Integration" handler_interface="Magento\Integration\Test\Handler\Integration\IntegrationInterface" class="Magento\Integration\Test\Fixture\Integration"> - + diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.php b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.php index b57e65919f29f..ed72ad49b1013 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.php @@ -21,7 +21,7 @@ * 6. Perform all assertions. * * @group Web_API_Framework_(PS) - * @ZephyrId MAGETWO-26009 + * @ZephyrId MAGETWO-26009, MAGETWO-16755, MAGETWO-16819, MAGETWO-16820 */ class CreateIntegrationEntityTest extends Injectable { diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml index 1ac9ad966192f..a908c02ec1d8e 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml @@ -7,7 +7,7 @@ --> - + Integration%isolation% test@example.com https://endpoint.com @@ -17,7 +17,7 @@ - + Integration%isolation% Custom Sales/Operations/Invoices @@ -26,12 +26,139 @@ - + Integration%isolation% All + + <script>alert('XSS-%isolation%')</script> + - + <script>alert('XSS')</script> + <script>alert('XSS')</script> + All + + + + + + + <IMG SRC=javascript:alert('XSS-%isolation%')> + - + <IMG SRC=javascript:alert('XSS')> + <IMG SRC=javascript:alert('XSS')> + All + + + + + + + name-%isolation%' OR 'a'='a + - + endpoint' OR 'a'='a + link' OR 'a'='a + All + + + + + + + name-%isolation%" OR "a"="a + - + endpoint" OR "a"="a + link" OR "a"="a + All + + + + + + + name-%isolation%" OR 'a"='a + - + endpoint" OR 'a"='a + link" OR 'a"='a + All + + + + + + + Integration%isolation% + abc.example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + abc.@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + abc..123@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + a@b@c@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + a"b(c)d,e:f;g<h>i[j\k]l@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + this\ still\"not\\allowed@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + just"not"right@example.com + https://endpoint.com + https://testlink.com + All + + + + + Integration%isolation% + this is"not\allowed@example.com + https://endpoint.com + https://testlink.com + All + + + diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.php b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.php new file mode 100644 index 0000000000000..e2f1411b45af1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.php @@ -0,0 +1,83 @@ + Extensions > Integrations + * 3. Click 'Add New Integration' + * 4. Fill in all required data + * 5. Click "Save" button to save Integration1 + * 6. Click 'Add New Integration' + * 7. Fill in all required data and use the same name as for Integration1 + * 8. Click "Save" button + * 9. Perform all assertions + * + * @group Web_API_Framework_(PS) + * @ZephyrId MAGETWO-16756 + */ +class CreateIntegrationWithDuplicatedNameTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const DOMAIN = 'PS'; + /* end tags */ + + /** + * Integration grid page. + * + * @var IntegrationIndex + */ + protected $integrationIndexPage; + + /** + * Integration new page. + * + * @var IntegrationNew + */ + protected $integrationNewPage; + + /** + * Injection data. + * + * @param IntegrationIndex $integrationIndex + * @param IntegrationNew $integrationNew + * @return void + */ + public function __inject( + IntegrationIndex $integrationIndex, + IntegrationNew $integrationNew + ) { + $this->integrationIndexPage = $integrationIndex; + $this->integrationNewPage = $integrationNew; + } + + /** + * Create Integration Entity with existing name test. + * + * @param Integration $integration + * @return array + */ + public function test(Integration $integration) + { + // Precondition + $integration->persist(); + + // Steps + $this->integrationIndexPage->open(); + $this->integrationIndexPage->getGridPageActions()->addNew(); + $this->integrationNewPage->getIntegrationForm()->fill($integration); + $this->integrationNewPage->getFormPageActions()->saveNew(); + return ['integration' => $integration]; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml new file mode 100644 index 0000000000000..e7f54bb994ca2 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationWithDuplicatedNameTest.xml @@ -0,0 +1,15 @@ + + + + + + default + + + + diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/EditTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/EditTest.php new file mode 100644 index 0000000000000..76905b3097756 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/EditTest.php @@ -0,0 +1,26 @@ +get( + 'Magento\Framework\View\LayoutInterface' + )->createBlock( + 'Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Edit' + ); + + $this->assertEquals('Encryption Key', $block->getHeaderText()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/FormTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/FormTest.php new file mode 100644 index 0000000000000..7315cd9f10f3e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/FormTest.php @@ -0,0 +1,54 @@ +get('Magento\Framework\View\DesignInterface') + ->setArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) + ->setDefaultDesignTheme(); + + $block = $objectManager->get('Magento\Framework\View\LayoutInterface') + ->createBlock('Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Form'); + + $prepareFormMethod = new \ReflectionMethod( + 'Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Form', + '_prepareForm' + ); + $prepareFormMethod->setAccessible(true); + $prepareFormMethod->invoke($block); + + $form = $block->getForm(); + + $this->assertEquals('edit_form', $form->getId()); + $this->assertEquals('post', $form->getMethod()); + + foreach (['enc_key_note', 'generate_random', 'crypt_key', 'main_fieldset'] as $id) { + $element = $form->getElement($id); + $this->assertNotNull($element); + } + + $generateRandomField = $form->getElement('generate_random'); + $this->assertEquals('select', $generateRandomField->getType()); + $this->assertEquals([ 0 => 'No', 1 => 'Yes'], $generateRandomField->getOptions()); + + $cryptKeyField = $form->getElement('crypt_key'); + $this->assertEquals('text', $cryptKeyField->getType()); + $this->assertEquals('crypt_key', $cryptKeyField->getName()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/IndexTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/IndexTest.php new file mode 100644 index 0000000000000..6d892bb9d8861 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/IndexTest.php @@ -0,0 +1,20 @@ +dispatch('backend/admin/crypt_key/index'); + + $body = $this->getResponse()->getBody(); + $this->assertContains('

Encryption Key

', $body); + } +} diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php new file mode 100644 index 0000000000000..e0b0a11cd72f8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php @@ -0,0 +1,105 @@ +getRequest(); + $request + ->setPostValue('generate_random', $ifGenerateRandom) + ->setPostValue('crypt_key', $encryptionKey); + $this->dispatch('backend/admin/crypt_key/save'); + $this->assertSessionMessages( + $this->contains('Please enter an encryption key.'), + \Magento\Framework\Message\MessageInterface::TYPE_ERROR + ); + } + + /** + * Test save action with provided encryption key + * + * @magentoDbIsolation enabled + */ + public function testSaveActionWithProvidedKey() + { + $this->markTestSkipped('Test is blocked by MAGETWO-33612.'); + + // data set with provided encryption key + $ifGenerateRandom = '0'; + $encryptionKey = 'foo_encryption_key'; + + $request = $this->getRequest(); + $request + ->setPostValue('generate_random', $ifGenerateRandom) + ->setPostValue('crypt_key', $encryptionKey); + $this->dispatch('backend/admin/crypt_key/save'); + + $this->assertRedirect(); + $this->assertSessionMessages( + $this->contains('The encryption key has been changed.'), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Test save action with invalid encryption key + */ + public function testSaveActionWithInvalidKey() + { + // data set with provided encryption key + $ifGenerateRandom = '0'; + $encryptionKey = 'invalid key'; + + $params = [ + 'generate_random' => $ifGenerateRandom, + 'crypt_key' => $encryptionKey, + ]; + $this->getRequest()->setPostValue($params); + $this->dispatch('backend/admin/crypt_key/save'); + + $this->assertRedirect(); + $this->assertSessionMessages( + $this->contains('The encryption key format is invalid.'), + \Magento\Framework\Message\MessageInterface::TYPE_ERROR + ); + } + + /** + * Test save action with randomly generated key + * + * @magentoDbIsolation enabled + */ + public function testSaveActionWithRandomKey() + { + $this->markTestSkipped('Test is blocked by MAGETWO-33612.'); + + // data set with random encryption key + $ifGenerateRandom = '1'; + $encryptionKey = ''; + + $request = $this->getRequest(); + $request + ->setPostValue('generate_random', $ifGenerateRandom) + ->setPostValue('crypt_key', $encryptionKey); + $this->dispatch('backend/admin/crypt_key/save'); + + $this->assertRedirect(); + $this->assertSessionMessages( + $this->contains('The encryption key has been changed.'), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/Resource/Key/ChangeTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/Resource/Key/ChangeTest.php new file mode 100644 index 0000000000000..494b050594c39 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/Resource/Key/ChangeTest.php @@ -0,0 +1,98 @@ +objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Deployment configuration file is not writable + */ + public function testChangeEncryptionKeyConfigNotWritable() + { + $writerMock = $this->getMock('Magento\Framework\App\DeploymentConfig\Writer', [], [], '', false); + $writerMock->expects($this->once())->method('checkIfWritable')->will($this->returnValue(false)); + + /** @var \Magento\EncryptionKey\Model\Resource\Key\Change $keyChangeModel */ + $keyChangeModel = $this->objectManager->create( + 'Magento\EncryptionKey\Model\Resource\Key\Change', + ['writer' => $writerMock] + ); + $keyChangeModel->changeEncryptionKey(); + } + + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/EncryptionKey/_files/payment_info.php + */ + public function testChangeEncryptionKey() + { + $testPath = 'test/config'; + $testValue = 'test'; + + $writerMock = $this->getMock('Magento\Framework\App\DeploymentConfig\Writer', [], [], '', false); + $writerMock->expects($this->once())->method('checkIfWritable')->will($this->returnValue(true)); + + $structureMock = $this->getMock('Magento\Config\Model\Config\Structure', [], [], '', false); + $structureMock->expects($this->once()) + ->method('getFieldPathsByAttribute') + ->will($this->returnValue([$testPath])); + + /** @var \Magento\EncryptionKey\Model\Resource\Key\Change $keyChangeModel */ + $keyChangeModel = $this->objectManager->create( + 'Magento\EncryptionKey\Model\Resource\Key\Change', + ['structure' => $structureMock, 'writer' => $writerMock] + ); + + $configModel = $this->objectManager->create( + 'Magento\Config\Model\Resource\Config' + ); + $configModel->saveConfig($testPath, 'test', 'default', 0); + $this->assertNotNull($keyChangeModel->changeEncryptionKey()); + + $connection = $keyChangeModel->getConnection(); + // Verify that the config value has been encrypted + $values1 = $connection->fetchPairs( + $connection->select()->from( + $keyChangeModel->getTable('core_config_data'), + ['config_id', 'value'] + )->where( + 'path IN (?)', + [$testPath] + )->where( + 'value NOT LIKE ?', + '' + ) + ); + $this->assertNotContains($testValue, $values1); + + // Verify that the credit card number has been encrypted + $values2 = $connection->fetchPairs( + $connection->select()->from( + $keyChangeModel->getTable('sales_order_payment'), + ['entity_id', 'cc_number_enc'] + ) + ); + $this->assertNotContains('1111111111', $values2); + + /** clean up */ + $select = $connection->select()->from($configModel->getMainTable())->where('path=?', $testPath); + $this->assertNotEmpty($connection->fetchRow($select)); + $configModel->deleteConfig($testPath, 'default', 0); + $this->assertEmpty($connection->fetchRow($select)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/_files/payment_info.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/_files/payment_info.php new file mode 100644 index 0000000000000..3edd7cc9c1da8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/_files/payment_info.php @@ -0,0 +1,20 @@ +create('Magento\Sales\Model\Order\Payment'); +$paymentInfo->setMethod('Cc')->setData('cc_number_enc', '1111111111'); + +/** @var \Magento\Sales\Model\Order $order */ +$order = $objectManager->create('Magento\Sales\Model\Order'); +$order->setIncrementId( + '100000001' +)->setPayment( + $paymentInfo +); +$order->save(); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/ComposerInformationTest.php b/dev/tests/integration/testsuite/Magento/Framework/Composer/ComposerInformationTest.php index 75e7b277f9308..590a553274d7d 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/ComposerInformationTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/ComposerInformationTest.php @@ -29,11 +29,6 @@ class ComposerInformationTest extends \PHPUnit_Framework_TestCase */ private $composerJsonFinder; - /** - * @var \Magento\Framework\Filesystem - */ - private $filesystem; - public function setUp() { $this->objectManager = Bootstrap::getObjectManager(); @@ -81,8 +76,7 @@ public function testGetRequiredPhpVersion($composerDir) 'applicationFactory' => new MagentoComposerApplicationFactory( $this->composerJsonFinder, $this->directoryList - ), - 'filesystem' => $this->filesystem, + ) ] ); @@ -106,8 +100,7 @@ public function testGetRequiredExtensions($composerDir) 'applicationFactory' => new MagentoComposerApplicationFactory( $this->composerJsonFinder, $this->directoryList - ), - 'filesystem' => $this->filesystem, + ) ] ); @@ -133,8 +126,7 @@ public function testGetRootRequiredPackagesAndTypes($composerDir) 'applicationFactory' => new MagentoComposerApplicationFactory( $this->composerJsonFinder, $this->directoryList - ), - 'filesystem' => $this->filesystem, + ) ] ); @@ -144,41 +136,6 @@ public function testGetRootRequiredPackagesAndTypes($composerDir) $this->assertEquals('library', $requiredPackagesAndTypes['composer/composer']); } - public function testGetPackagesForUpdate() - { - $packageName = 'magento/language-de_de'; - - $this->setupDirectory('testSkeleton'); - - /** @var \Magento\Framework\Composer\ComposerInformation $composerInfo */ - $composerInfo = $this->objectManager->create( - 'Magento\Framework\Composer\ComposerInformation', - [ - 'applicationFactory' => new MagentoComposerApplicationFactory( - $this->composerJsonFinder, - $this->directoryList - ), - 'filesystem' => $this->filesystem, - ] - ); - - $requiredPackages = $composerInfo->getInstalledMagentoPackages(); - $this->assertArrayHasKey($packageName, $requiredPackages); - - $this->assertTrue($composerInfo->syncPackagesForUpdate()); - - $packagesForUpdate = $composerInfo->getPackagesForUpdate(); - $this->assertArrayHasKey('packages', $packagesForUpdate); - $this->assertArrayHasKey($packageName, $packagesForUpdate['packages']); - $this->assertTrue( - version_compare( - $packagesForUpdate['packages'][$packageName]['latestVersion'], - $requiredPackages[$packageName]['version'], - '>' - ) - ); - } - /** * Data provider that returns directories containing different types of composer files. * @@ -206,8 +163,7 @@ public function testNoLock() 'applicationFactory' => new MagentoComposerApplicationFactory( $this->composerJsonFinder, $this->directoryList - ), - 'filesystem' => $this->filesystem, + ) ] ); } @@ -223,8 +179,7 @@ public function testIsPackageInComposerJson() 'applicationFactory' => new MagentoComposerApplicationFactory( $this->composerJsonFinder, $this->directoryList - ), - 'filesystem' => $this->filesystem, + ) ] ); diff --git a/dev/tests/integration/testsuite/Magento/Setup/Controller/ComponentGridTest.php b/dev/tests/integration/testsuite/Magento/Setup/Controller/ComponentGridTest.php index f9032e83e2e52..7fe1f34ec5c00 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Controller/ComponentGridTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Controller/ComponentGridTest.php @@ -8,14 +8,20 @@ use Magento\Framework\Composer\ComposerInformation; use Magento\Framework\Module\PackageInfo; +use Magento\Setup\Model\UpdatePackagesCache; class ComponentGridTest extends \PHPUnit_Framework_TestCase { /** - * @var ComposerInformation + * @var ComposerInformation|\PHPUnit_Framework_MockObject_MockObject */ private $composerInformationMock; + /** + * @var UpdatePackagesCache|\PHPUnit_Framework_MockObject_MockObject + */ + private $updatePackagesCacheMock; + /** * Module package info * @@ -101,12 +107,21 @@ public function __construct() '', false ); + $this->updatePackagesCacheMock = $this->getMock( + 'Magento\Setup\Model\UpdatePackagesCache', + [], + [], + '', + false + ); + $packageInfoFactory->expects($this->once()) ->method('create') ->willReturn($this->packageInfo); $this->controller = new ComponentGrid( $this->composerInformationMock, - $objectManagerProvider + $objectManagerProvider, + $this->updatePackagesCacheMock ); } @@ -128,7 +143,7 @@ public function testComponentsAction() $this->composerInformationMock->expects($this->once()) ->method('isPackageInComposerJson') ->willReturn(true); - $this->composerInformationMock->expects($this->once()) + $this->updatePackagesCacheMock->expects($this->once()) ->method('getPackagesForUpdate') ->willReturn($this->lastSyncData); $jsonModel = $this->controller->componentsAction(); @@ -153,9 +168,9 @@ public function testComponentsAction() public function testSyncAction() { - $this->composerInformationMock->expects($this->once()) + $this->updatePackagesCacheMock->expects($this->once()) ->method('syncPackagesForUpdate'); - $this->composerInformationMock->expects($this->once()) + $this->updatePackagesCacheMock->expects($this->once()) ->method('getPackagesForUpdate') ->willReturn($this->lastSyncData); $jsonModel = $this->controller->syncAction(); diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/UpdatePackagesCacheTest.php b/dev/tests/integration/testsuite/Magento/Setup/Model/UpdatePackagesCacheTest.php new file mode 100644 index 0000000000000..2e94d3898f3d5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/UpdatePackagesCacheTest.php @@ -0,0 +1,115 @@ +objectManager = Bootstrap::getObjectManager(); + } + + /** + * Setup DirectoryList, Filesystem, and ComposerJsonFinder to use a specified directory for reading composer files + * + * @param $composerDir string Directory under _files that contains composer files + */ + private function setupDirectory($composerDir) + { + $absoluteComposerDir = realpath(__DIR__ . '/_files/' . $composerDir . '/composer.json'); + $this->composerJsonFinder = $this->getMockBuilder('Magento\Framework\Composer\ComposerJsonFinder') + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $this->composerJsonFinder->expects($this->any())->method('findComposerJson')->willReturn($absoluteComposerDir); + + $this->directoryList = $this->objectManager->get('Magento\Framework\App\Filesystem\DirectoryList'); + $this->filesystem = $this->objectManager->get('Magento\Framework\Filesystem'); + + /** @var \Magento\Framework\Composer\ComposerInformation $composerInfo */ + $this->composerInformation = $this->objectManager->create( + 'Magento\Framework\Composer\ComposerInformation', + [ + 'applicationFactory' => new MagentoComposerApplicationFactory( + $this->composerJsonFinder, + $this->directoryList + ) + ] + ); + } + + public function testGetPackagesForUpdate() + { + $packageName = 'magento/module-store'; + + $this->setupDirectory('testSkeleton'); + + /** @var \Magento\Setup\Model\UpdatePackagesCache $updatePackagesCache */ + $updatePackagesCache = $this->objectManager->create( + 'Magento\Setup\Model\UpdatePackagesCache', + [ + 'applicationFactory' => new MagentoComposerApplicationFactory( + $this->composerJsonFinder, + $this->directoryList + ), + 'filesystem' => $this->filesystem, + 'dateTime' => new DateTime(), + 'composerInformation' => $this->composerInformation + ] + ); + + $requiredPackages = $this->composerInformation->getInstalledMagentoPackages(); + $this->assertArrayHasKey($packageName, $requiredPackages); + + $this->assertTrue($updatePackagesCache->syncPackagesForUpdate()); + + $packagesForUpdate = $updatePackagesCache->getPackagesForUpdate(); + $this->assertArrayHasKey('packages', $packagesForUpdate); + $this->assertArrayHasKey($packageName, $packagesForUpdate['packages']); + $this->assertTrue( + version_compare( + $packagesForUpdate['packages'][$packageName]['latestVersion'], + $requiredPackages[$packageName]['version'], + '>' + ) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.json b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.json new file mode 100644 index 0000000000000..945bccecd52ed --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.json @@ -0,0 +1,24 @@ +{ + "version": "0.74.0-beta9", + "require": { + "magento/product-community-edition": "0.74.0-beta2", + "magento/sample-module-minimal" : "*" + }, + "repositories": [ + { + "type": "composer", + "url": "http://packages.magento.com/" + } + ], + "autoload": { + "psr-4": { + "Magento\\Framework\\": "htdocs/lib/internal/Magento/Framework/", + "Magento\\Setup\\": "htdocs/setup/src/Magento/Setup/" + } + }, + "extra": { + "magento-root-dir": "htdocs", + "magento-deploystrategy": "copy" + }, + "minimum-stability": "dev" +} diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock new file mode 100644 index 0000000000000..0a8ab77a1d894 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock @@ -0,0 +1,6174 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "8601c8b464025318475d28dbabf9ee67", + "packages": [ + { + "name": "composer/composer", + "version": "1.0.0-alpha8", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "1eb1df44a97fb2daca1bb8b007f3bee012f0aa46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/1eb1df44a97fb2daca1bb8b007f3bee012f0aa46", + "reference": "1eb1df44a97fb2daca1bb8b007f3bee012f0aa46", + "shasum": "" + }, + "require": { + "justinrainbow/json-schema": "1.1.*", + "php": ">=5.3.2", + "seld/jsonlint": "1.*", + "symfony/console": "~2.3", + "symfony/finder": "~2.2", + "symfony/process": "~2.1" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.10" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives, and allows gzip compression of all internet traffic" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Composer": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be", + "role": "Developer" + }, + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de", + "role": "Developer" + } + ], + "description": "Dependency Manager", + "homepage": "http://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2014-01-06 18:39:59" + }, + { + "name": "justinrainbow/json-schema", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "05ff6d8d79fe3ad190b0663d80d3f9deee79416c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/05ff6d8d79fe3ad190b0663d80d3f9deee79416c", + "reference": "05ff6d8d79fe3ad190b0663d80d3f9deee79416c", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonSchema": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "NewBSD" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch", + "homepage": "http://wiedler.ch/igor/" + }, + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Robert Schönthal", + "email": "robert.schoenthal@gmail.com", + "homepage": "http://digitalkaoz.net" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2012-01-03 00:33:17" + }, + { + "name": "magento/framework", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_framework-0.74.0-beta2.zip", + "reference": null, + "shasum": "ebb865d9aae1b48e41706a7d2fe080b7735a1a0c" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-mcrypt": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "lib-libxml": "*", + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "ext-imagick": "Use Image Magick >=3.0.0 as an optional alternative image processing library" + }, + "type": "magento2-library", + "extra": { + "map": [ + [ + "*", + "Magento/Framework" + ] + ] + }, + "autoload": { + "psr-4": { + "Magento\\Framework\\": "" + } + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/language-de_de", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-de_de-0.74.0-beta2.zip", + "reference": null, + "shasum": "574416a036e55513a6437b287b77426c291d9051" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/de_de" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "German (Germany) language" + }, + { + "name": "magento/language-en_us", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-en_us-0.74.0-beta2.zip", + "reference": null, + "shasum": "6246a6a773d86c078bb4f0bc5cee50e2eeab639f" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/en_us" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "English (United States) language" + }, + { + "name": "magento/language-es_es", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-es_es-0.74.0-beta2.zip", + "reference": null, + "shasum": "da8197a083474993b935bd16d8b001160475b97e" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/es_es" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Spanish (Spain) language" + }, + { + "name": "magento/language-fr_fr", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-fr_fr-0.74.0-beta2.zip", + "reference": null, + "shasum": "e3d58180b96a709e0b2fb8dccaee95b3be5dadd4" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/fr_fr" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "French (France) language" + }, + { + "name": "magento/language-nl_nl", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-nl_nl-0.74.0-beta2.zip", + "reference": null, + "shasum": "d7b29b6275aefa26e7a6d9fb4bcdb62a8ee34332" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/nl_nl" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Dutch (Netherlands) language" + }, + { + "name": "magento/language-pt_br", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-pt_br-0.74.0-beta2.zip", + "reference": null, + "shasum": "7b6bc50f18adc85d524cc549accd05752beb8858" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/pt_br" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Portuguese (Brazil) language" + }, + { + "name": "magento/language-zh_cn", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_language-zh_cn-0.74.0-beta2.zip", + "reference": null, + "shasum": "f9a151b4d1bbc6620289e9e91fb8bee023d14c5f" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*" + }, + "type": "magento2-language", + "extra": { + "map": [ + [ + "*", + "magento/zh_cn" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Chinese (China) language" + }, + { + "name": "magento/magento-composer-installer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/magento/magento-composer-installer.git", + "reference": "7f03451f71e55d52c2bb07325d56a4e6df322f30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/7f03451f71e55d52c2bb07325d56a4e6df322f30", + "reference": "7f03451f71e55d52c2bb07325d56a4e6df322f30", + "shasum": "" + }, + "require": { + "composer-plugin-api": "1.0.0" + }, + "require-dev": { + "composer/composer": "*@dev", + "firegento/phpcs": "dev-patch-1", + "mikey179/vfsstream": "*", + "phpunit/phpunit": "*", + "phpunit/phpunit-mock-objects": "dev-master", + "squizlabs/php_codesniffer": "1.4.7", + "symfony/process": "*" + }, + "type": "composer-plugin", + "extra": { + "composer-command-registry": [ + "MagentoHackathon\\Composer\\Magento\\Command\\DeployCommand" + ], + "class": "MagentoHackathon\\Composer\\Magento\\Plugin" + }, + "autoload": { + "psr-0": { + "MagentoHackathon\\Composer\\Magento": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Vinai Kopp", + "email": "vinai@netzarbeiter.com" + }, + { + "name": "Daniel Fahlke aka Flyingmana", + "email": "flyingmana@googlemail.com" + }, + { + "name": "Jörg Weller", + "email": "weller@flagbit.de" + }, + { + "name": "Karl Spies", + "email": "karl.spies@gmx.net" + }, + { + "name": "Tobias Vogt", + "email": "tobi@webguys.de" + }, + { + "name": "David Fuhr", + "email": "fuhr@flagbit.de" + } + ], + "description": "Composer installer for Magento modules", + "homepage": "https://github.com/magento/magento-composer-installer", + "keywords": [ + "composer-installer", + "magento" + ], + "time": "2015-03-05 21:40:30" + }, + { + "name": "magento/magento2-base", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_magento2-base-0.74.0-beta2.zip", + "reference": null, + "shasum": "be10c634940670ff0cecf5f952f57891c626c652" + }, + "require": { + "magento/magento-composer-installer": "*" + }, + "replace": { + "blueimp/jquery-file-upload": "5.6.14", + "colinmollenhour/cache-backend-redis": "dev-master#193d377b7fb2e88595578b282fa01a62d1185abc", + "colinmollenhour/credis": "dev-master#f07bbfd4117294f462f0fb19c49221d350bf396f", + "components/jquery": "1.11.0", + "components/jqueryui": "1.10.4", + "linkorb/jsmin-php": "1.1.2", + "tinymce/tinymce": "3.4.7", + "trentrichardson/jquery-timepicker-addon": "1.4.3", + "twbs/bootstrap": "3.1.0" + }, + "type": "magento2-component", + "extra": { + "component_paths": { + "trentrichardson/jquery-timepicker-addon": "lib/web/jquery/jquery-ui-timepicker-addon.js", + "colinmollenhour/cache-backend-redis": "lib/internal/Cm/Cache/Backend/Redis.php", + "colinmollenhour/credis": "lib/internal/Credis", + "linkorb/jsmin-php": "lib/internal/JSMin", + "components/jquery": [ + "lib/web/jquery.js", + "lib/web/jquery/jquery.min.js", + "lib/web/jquery/jquery-migrate.js", + "lib/web/jquery/jquery-migrate.min.js" + ], + "blueimp/jquery-file-upload": "lib/web/jquery/fileUploader", + "components/jqueryui": [ + "lib/web/jquery/jquery-ui.js", + "lib/web/jquery/jquery-ui.min.js" + ], + "twbs/bootstrap": [ + "lib/web/jquery/jquery.tabs.js" + ], + "tinymce/tinymce": "lib/web/tiny_mce" + }, + "map": [ + [ + "lib/internal/Cm", + "lib/internal/Cm" + ], + [ + "lib/internal/LinLibertineFont", + "lib/internal/LinLibertineFont" + ], + [ + "lib/internal/Credis", + "lib/internal/Credis" + ], + [ + "lib/internal/CardinalCommerce", + "lib/internal/CardinalCommerce" + ], + [ + "lib/internal/JSMin", + "lib/internal/JSMin" + ], + [ + "lib/.htaccess", + "lib/.htaccess" + ], + [ + "lib/web/lib", + "lib/web/lib" + ], + [ + "lib/web/requirejs", + "lib/web/requirejs" + ], + [ + "lib/web/prototype", + "lib/web/prototype" + ], + [ + "lib/web/moment.js", + "lib/web/moment.js" + ], + [ + "lib/web/i18n", + "lib/web/i18n" + ], + [ + "lib/web/varien", + "lib/web/varien" + ], + [ + "lib/web/blank.html", + "lib/web/blank.html" + ], + [ + "lib/web/scriptaculous", + "lib/web/scriptaculous" + ], + [ + "lib/web/ko", + "lib/web/ko" + ], + [ + "lib/web/modernizr", + "lib/web/modernizr" + ], + [ + "lib/web/date-format-normalizer.js", + "lib/web/date-format-normalizer.js" + ], + [ + "lib/web/spacer.gif", + "lib/web/spacer.gif" + ], + [ + "lib/web/images", + "lib/web/images" + ], + [ + "lib/web/matchMedia.js", + "lib/web/matchMedia.js" + ], + [ + "lib/web/extjs", + "lib/web/extjs" + ], + [ + "lib/web/css", + "lib/web/css" + ], + [ + "lib/web/fonts", + "lib/web/fonts" + ], + [ + "lib/web/jquery", + "lib/web/jquery" + ], + [ + "lib/web/less", + "lib/web/less" + ], + [ + "lib/web/jquery.js", + "lib/web/jquery.js" + ], + [ + "lib/web/underscore.js", + "lib/web/underscore.js" + ], + [ + "lib/web/legacy-build.min.js", + "lib/web/legacy-build.min.js" + ], + [ + "lib/web/mage", + "lib/web/mage" + ], + [ + "lib/web/tiny_mce", + "lib/web/tiny_mce" + ], + [ + ".htaccess.sample", + ".htaccess.sample" + ], + [ + "CONTRIBUTING.md", + "CONTRIBUTING.md" + ], + [ + "package.json", + "package.json" + ], + [ + "nginx.conf.sample", + "nginx.conf.sample" + ], + [ + "var/.htaccess", + "var/.htaccess" + ], + [ + "pub/media/downloadable/.htaccess", + "pub/media/downloadable/.htaccess" + ], + [ + "pub/media/theme_customization/.htaccess", + "pub/media/theme_customization/.htaccess" + ], + [ + "pub/media/customer/.htaccess", + "pub/media/customer/.htaccess" + ], + [ + "pub/media/.htaccess", + "pub/media/.htaccess" + ], + [ + "pub/errors", + "pub/errors" + ], + [ + "pub/opt", + "pub/opt" + ], + [ + "pub/get.php", + "pub/get.php" + ], + [ + "pub/static/.htaccess", + "pub/static/.htaccess" + ], + [ + "pub/.htaccess", + "pub/.htaccess" + ], + [ + "pub/static.php", + "pub/static.php" + ], + [ + "pub/cron.php", + "pub/cron.php" + ], + [ + "pub/index.php", + "pub/index.php" + ], + [ + ".php_cs", + ".php_cs" + ], + [ + "COPYING.txt", + "COPYING.txt" + ], + [ + "dev/shell", + "dev/shell" + ], + [ + "dev/tools", + "dev/tools" + ], + [ + "dev/tests/integration/phpunit.xml.dist", + "dev/tests/integration/phpunit.xml.dist" + ], + [ + "dev/tests/integration/etc", + "dev/tests/integration/etc" + ], + [ + "dev/tests/integration/tmp", + "dev/tests/integration/tmp" + ], + [ + "dev/tests/integration/framework", + "dev/tests/integration/framework" + ], + [ + "dev/tests/integration/testsuite/Magento", + "dev/tests/integration/testsuite/Magento" + ], + [ + "dev/tests/integration/.gitignore", + "dev/tests/integration/.gitignore" + ], + [ + "dev/tests/functional/composer.json", + "dev/tests/functional/composer.json" + ], + [ + "dev/tests/functional/lib", + "dev/tests/functional/lib" + ], + [ + "dev/tests/functional/phpunit.xml.dist", + "dev/tests/functional/phpunit.xml.dist" + ], + [ + "dev/tests/functional/composer.json.dist", + "dev/tests/functional/composer.json.dist" + ], + [ + "dev/tests/functional/bootstrap.php", + "dev/tests/functional/bootstrap.php" + ], + [ + "dev/tests/functional/credentials.xml.dist", + "dev/tests/functional/credentials.xml.dist" + ], + [ + "dev/tests/functional/tests", + "dev/tests/functional/tests" + ], + [ + "dev/tests/functional/utils", + "dev/tests/functional/utils" + ], + [ + "dev/tests/functional/isolation.php", + "dev/tests/functional/isolation.php" + ], + [ + "dev/tests/functional/etc", + "dev/tests/functional/etc" + ], + [ + "dev/tests/functional/.htaccess", + "dev/tests/functional/.htaccess" + ], + [ + "dev/tests/functional/testsuites/Magento", + "dev/tests/functional/testsuites/Magento" + ], + [ + "dev/tests/functional/.gitignore", + "dev/tests/functional/.gitignore" + ], + [ + "dev/tests/js/run_js_tests.php", + "dev/tests/js/run_js_tests.php" + ], + [ + "dev/tests/js/jsTestDriverOrder.php", + "dev/tests/js/jsTestDriverOrder.php" + ], + [ + "dev/tests/js/jsTestDriver.php.dist", + "dev/tests/js/jsTestDriver.php.dist" + ], + [ + "dev/tests/js/spec", + "dev/tests/js/spec" + ], + [ + "dev/tests/js/framework", + "dev/tests/js/framework" + ], + [ + "dev/tests/js/testsuite/lib", + "dev/tests/js/testsuite/lib" + ], + [ + "dev/tests/js/testsuite/mage", + "dev/tests/js/testsuite/mage" + ], + [ + "dev/tests/js/.gitignore", + "dev/tests/js/.gitignore" + ], + [ + "dev/tests/api-functional/phpunit.xml.dist", + "dev/tests/api-functional/phpunit.xml.dist" + ], + [ + "dev/tests/api-functional/config", + "dev/tests/api-functional/config" + ], + [ + "dev/tests/api-functional/framework", + "dev/tests/api-functional/framework" + ], + [ + "dev/tests/api-functional/_files", + "dev/tests/api-functional/_files" + ], + [ + "dev/tests/api-functional/testsuite/Magento", + "dev/tests/api-functional/testsuite/Magento" + ], + [ + "dev/tests/api-functional/.gitignore", + "dev/tests/api-functional/.gitignore" + ], + [ + "dev/tests/unit/phpunit.xml.dist", + "dev/tests/unit/phpunit.xml.dist" + ], + [ + "dev/tests/unit/tmp", + "dev/tests/unit/tmp" + ], + [ + "dev/tests/unit/framework", + "dev/tests/unit/framework" + ], + [ + "dev/tests/unit/.gitignore", + "dev/tests/unit/.gitignore" + ], + [ + "dev/tests/static/phpunit-all.xml.dist", + "dev/tests/static/phpunit-all.xml.dist" + ], + [ + "dev/tests/static/phpunit.xml.dist", + "dev/tests/static/phpunit.xml.dist" + ], + [ + "dev/tests/static/framework", + "dev/tests/static/framework" + ], + [ + "dev/tests/static/testsuite/Magento", + "dev/tests/static/testsuite/Magento" + ], + [ + "dev/tests/static/.gitignore", + "dev/tests/static/.gitignore" + ], + [ + "dev/tests/performance/benchmark.jmx", + "dev/tests/performance/benchmark.jmx" + ], + [ + "dev/tests/performance/compare_reports.php", + "dev/tests/performance/compare_reports.php" + ], + [ + "dev/tests/performance/run_scenarios.php", + "dev/tests/performance/run_scenarios.php" + ], + [ + "dev/tests/performance/framework", + "dev/tests/performance/framework" + ], + [ + "dev/tests/performance/testsuite/backend.jmx", + "dev/tests/performance/testsuite/backend.jmx" + ], + [ + "dev/tests/performance/testsuite/product_edit.jmx", + "dev/tests/performance/testsuite/product_edit.jmx" + ], + [ + "dev/tests/performance/testsuite/add_to_cart.jmx", + "dev/tests/performance/testsuite/add_to_cart.jmx" + ], + [ + "dev/tests/performance/testsuite/product_view.jmx", + "dev/tests/performance/testsuite/product_view.jmx" + ], + [ + "dev/tests/performance/testsuite/home_page.jmx", + "dev/tests/performance/testsuite/home_page.jmx" + ], + [ + "dev/tests/performance/testsuite/reusable", + "dev/tests/performance/testsuite/reusable" + ], + [ + "dev/tests/performance/testsuite/quick_search.jmx", + "dev/tests/performance/testsuite/quick_search.jmx" + ], + [ + "dev/tests/performance/testsuite/fixtures", + "dev/tests/performance/testsuite/fixtures" + ], + [ + "dev/tests/performance/testsuite/advanced_search.jmx", + "dev/tests/performance/testsuite/advanced_search.jmx" + ], + [ + "dev/tests/performance/testsuite/checkout.jmx", + "dev/tests/performance/testsuite/checkout.jmx" + ], + [ + "dev/tests/performance/testsuite/category_view.jmx", + "dev/tests/performance/testsuite/category_view.jmx" + ], + [ + "dev/tests/performance/testsuite/_samples", + "dev/tests/performance/testsuite/_samples" + ], + [ + "dev/tests/performance/config.php.dist", + "dev/tests/performance/config.php.dist" + ], + [ + "dev/tests/performance/.gitignore", + "dev/tests/performance/.gitignore" + ], + [ + "dev/.htaccess", + "dev/.htaccess" + ], + [ + "Gruntfile.js", + "Gruntfile.js" + ], + [ + "setup", + "setup" + ], + [ + "LICENSE.txt", + "LICENSE.txt" + ], + [ + ".travis.yml", + ".travis.yml" + ], + [ + "app/bootstrap.php", + "app/bootstrap.php" + ], + [ + "app/etc/di.xml", + "app/etc/di.xml" + ], + [ + "app/.htaccess", + "app/.htaccess" + ], + [ + "app/functions.php", + "app/functions.php" + ], + [ + "app/autoload.php", + "app/autoload.php" + ], + [ + ".htaccess", + ".htaccess" + ], + [ + "php.ini.sample", + "php.ini.sample" + ], + [ + "CONTRIBUTOR_LICENSE_AGREEMENT.html", + "CONTRIBUTOR_LICENSE_AGREEMENT.html" + ], + [ + "index.php", + "index.php" + ], + [ + "LICENSE_AFL.txt", + "LICENSE_AFL.txt" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Magento 2 Base" + }, + { + "name": "magento/module-admin-notification", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-admin-notification-0.74.0-beta2.zip", + "reference": null, + "shasum": "740186bdf97b2161994c10a883211eae76b285e9" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/AdminNotification" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-authorization", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-authorization-0.74.0-beta2.zip", + "reference": null, + "shasum": "045f8440fe82b38ce228b0be52b011bb02773214" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Authorization" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Authorization module provides access to Magento ACL functionality." + }, + { + "name": "magento/module-backend", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-backend-0.74.0-beta2.zip", + "reference": null, + "shasum": "39ddbf62a11d9be1b16539733c0f66f46a503033" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backup": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-cron": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-developer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-reports": "0.74.0-beta2", + "magento/module-require-js": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-translation": "0.74.0-beta2", + "magento/module-user": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Backend" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-backup", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-backup-0.74.0-beta2.zip", + "reference": null, + "shasum": "7188d9a41bd9d4294dfd90c7053c301876d82c48" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-cron": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Backup" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-bundle", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-bundle-0.74.0-beta2.zip", + "reference": null, + "shasum": "849f7fc43f7f9b6a37a922272656c6218c681693" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-catalog-rule": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-gift-message": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-webapi": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Bundle" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-captcha", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-captcha-0.74.0-beta2.zip", + "reference": null, + "shasum": "a80050a22bad3399e146b242be33f6717fe15d26" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Captcha" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-0.74.0-beta2.zip", + "reference": null, + "shasum": "d4259a0113a3b8eece9bdf88ad24b5cdee349de0" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-catalog-rule": "0.74.0-beta2", + "magento/module-catalog-url-rewrite": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-msrp": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "magento/module-product-alert": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-url-rewrite": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-cookie": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Catalog" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "3a26206abf71888d300c9d72b2190ff0f66eafd2" + }, + "require": { + "ext-ctype": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-inventory", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-inventory-0.74.0-beta2.zip", + "reference": null, + "shasum": "805c2bb2fb2c2f038dd73e4030a02e1208ea23da" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogInventory" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-rule", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-rule-0.74.0-beta2.zip", + "reference": null, + "shasum": "e9fe8b0f65dea1f8feba125e7d2974d48d1e0eef" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-rule": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogRule" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-search", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-search-0.74.0-beta2.zip", + "reference": null, + "shasum": "e8239e3dbbdccda266347d1ca5f286c1e9a2cb4b" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-search": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogSearch" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-url-rewrite", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-url-rewrite-0.74.0-beta2.zip", + "reference": null, + "shasum": "c3cc582396cc97970e2d0dd92e5315190cde0971" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-import-export": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-url-rewrite": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogUrlRewrite" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-catalog-widget", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-catalog-widget-0.74.0-beta2.zip", + "reference": null, + "shasum": "7dad3ad909bc97610211f4f008fa1817f1aac22a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-rule": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CatalogWidget" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-centinel", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-centinel-0.74.0-beta2.zip", + "reference": null, + "shasum": "069e94cd124c0d078eeb3174afdc1e49031a7288" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Centinel" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-checkout", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-checkout-0.74.0-beta2.zip", + "reference": null, + "shasum": "8161dcc6c2bba44981cb63cd24cf137900d37db7" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-gift-message": "0.74.0-beta2", + "magento/module-msrp": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-cookie": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Checkout" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-checkout-agreements", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-checkout-agreements-0.74.0-beta2.zip", + "reference": null, + "shasum": "be00ed9e3c1e3e4bacf7efc00b67c7cbb2065715" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CheckoutAgreements" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-cms", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-cms-0.74.0-beta2.zip", + "reference": null, + "shasum": "15ae95a27b5ff0c82fbdb43ca8570e0cee4b21f5" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-email": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "magento/module-variable": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Cms" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-cms-url-rewrite", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-cms-url-rewrite-0.74.0-beta2.zip", + "reference": null, + "shasum": "6a3775d7705efcf5b43bfbe327d6ef1db2a1c09c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-cms": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-url-rewrite": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CmsUrlRewrite" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-config", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-config-0.74.0-beta2.zip", + "reference": null, + "shasum": "a4be4800b197ab3a73bbfc0d23d5a1f753a07a57" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-cron": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-email": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Config" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-configurable-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-configurable-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "bb6d242228bcbe7c35253dc244c7977994b24b8e" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-import-export": "0.74.0-beta2", + "magento/module-configurable-product": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/ConfigurableImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-configurable-product", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-configurable-product-0.74.0-beta2.zip", + "reference": null, + "shasum": "fe8e4536a59f60be79211fc0072e640102d3be06" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-catalog-rule": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-webapi": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/ConfigurableProduct" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-contact", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-contact-0.74.0-beta2.zip", + "reference": null, + "shasum": "b53d65b91dd1f596d0a4f7b8bacf73e6eaff98c9" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-cms": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Contact" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-cookie", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-cookie-0.74.0-beta2.zip", + "reference": null, + "shasum": "a8a4e29967524214fb6af5c12681d7b410a63bc6" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-store": "0.74.0-beta2", + "php": "~5.4.11|~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-backend": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Cookie" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-cron", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-cron-0.74.0-beta2.zip", + "reference": null, + "shasum": "0e91ec4ade457797e7db0fe1aa4794747817fa6a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-config": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Cron" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-currency-symbol", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-currency-symbol-0.74.0-beta2.zip", + "reference": null, + "shasum": "261c0a4e05295f852a7986ef2d15509e4c2ad26c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CurrencySymbol" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-customer", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-customer-0.74.0-beta2.zip", + "reference": null, + "shasum": "135623f9517f12f4dc17e062280eaf5f5071e7b7" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-integration": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-newsletter": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-review": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-cookie": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Customer" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-customer-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-customer-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "9b4f6b059fecce79c7f8ce9fc163858b8a47c7ca" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/CustomerImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-developer", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-developer-0.74.0-beta2.zip", + "reference": null, + "shasum": "1a1077d963ec6310df7080defaadc9bd87ed6e21" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Developer" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-dhl", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-dhl-0.74.0-beta2.zip", + "reference": null, + "shasum": "f525d0b48189b60e2c0def9a8804933133995104" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Dhl" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-directory", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-directory-0.74.0-beta2.zip", + "reference": null, + "shasum": "3cb3ed72d237966f9deab3ac0386c3b6701cf66e" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Directory" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-downloadable", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-downloadable-0.74.0-beta2.zip", + "reference": null, + "shasum": "5bd1cabf95a5c44e329f1263f40e165b3aa6070e" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-gift-message": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-msrp": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Downloadable" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-eav", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-eav-0.74.0-beta2.zip", + "reference": null, + "shasum": "5594f375105dc1921f38772fbb069ff57c2e8f01" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Eav" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-email", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-email-0.74.0-beta2.zip", + "reference": null, + "shasum": "8ff7affa3d22107bd5e519dc24c098acccc44bf8" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-variable": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Email" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-fedex", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-fedex-0.74.0-beta2.zip", + "reference": null, + "shasum": "cdc61b901b080b14e42b10889c8fb6f639f520af" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Fedex" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-gift-message", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-gift-message-0.74.0-beta2.zip", + "reference": null, + "shasum": "76ea03167970205295c95d88f871f7ce0547cdbf" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-multishipping": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GiftMessage" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-google-adwords", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-google-adwords-0.74.0-beta2.zip", + "reference": null, + "shasum": "45dfa690b1f183b0a87e6db39e6399ff0024bc66" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GoogleAdwords" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-google-analytics", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-google-analytics-0.74.0-beta2.zip", + "reference": null, + "shasum": "7460860110d6942c3375db4a05cf079134e8ab6a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-cookie": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GoogleAnalytics" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-google-optimizer", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-google-optimizer-0.74.0-beta2.zip", + "reference": null, + "shasum": "90565d6ecc18276ef08c324caae5178ca8ba82ed" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-google-analytics": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GoogleOptimizer" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-google-shopping", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-google-shopping-0.74.0-beta2.zip", + "reference": null, + "shasum": "f86c5696c215900805df27dcc6f8d6df5bd8453a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GoogleShopping" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-grouped-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-grouped-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "3cbf803098dd03d361b51f730930c9c3e6a9292c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-import-export": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-grouped-product": "0.74.0-beta2", + "magento/module-import-export": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GroupedImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-grouped-product", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-grouped-product-0.74.0-beta2.zip", + "reference": null, + "shasum": "8ad968c974e321d99a9bccfd04721ccfb4bfb86f" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-msrp": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/GroupedProduct" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "c16b13bdf9efb2a9d52b6423ee641ca47d60da36" + }, + "require": { + "ext-ctype": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-indexer": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/ImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-indexer", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-indexer-0.74.0-beta2.zip", + "reference": null, + "shasum": "d1c6e17c92d766f8f3f578d290da957deb5d9a7c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Indexer" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-integration", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-integration-0.74.0-beta2.zip", + "reference": null, + "shasum": "8e9a7d4df606513a67a649f63abdd31557061665" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-user": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Integration" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-layered-navigation", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-layered-navigation-0.74.0-beta2.zip", + "reference": null, + "shasum": "a20af59f1ffcf9565e2fab9ead134ee129bdcc8d" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/LayeredNavigation" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-media-storage", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-media-storage-0.74.0-beta2.zip", + "reference": null, + "shasum": "7c1b9232bf83b123c2a3c6b03973bd637f34e0f6" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/MediaStorage" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-msrp", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-msrp-0.74.0-beta2.zip", + "reference": null, + "shasum": "16bda583d7a876262fdc740c9513a4fb1739c3eb" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-bundle": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-downloadable": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-grouped-product": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Msrp" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-multishipping", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-multishipping-0.74.0-beta2.zip", + "reference": null, + "shasum": "53b01c1acc039dec9e603681264a450f708d5fb6" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Multishipping" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-newsletter", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-newsletter-0.74.0-beta2.zip", + "reference": null, + "shasum": "a9e11c0ab106ea0aec8709a5a17169be826680c3" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-cron": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-email": "0.74.0-beta2", + "magento/module-require-js": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Newsletter" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-offline-payments", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-offline-payments-0.74.0-beta2.zip", + "reference": null, + "shasum": "d3b7cd008994bef7456496094f397c82a9f3a8ef" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-payment": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/OfflinePayments" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-offline-shipping", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-offline-shipping-0.74.0-beta2.zip", + "reference": null, + "shasum": "94f097652ea87bc0504837366a51b7065ca864d4" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-sales-rule": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/OfflineShipping" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-page-cache", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-page-cache-0.74.0-beta2.zip", + "reference": null, + "shasum": "9db7b9c8d035e0eb3f619d37c8ff439b9f225e8f" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/PageCache" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-payment", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-payment-0.74.0-beta2.zip", + "reference": null, + "shasum": "9da0894c984887cf2e3c6c5a32f84616867495ae" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-centinel": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Payment" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-persistent", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-persistent-0.74.0-beta2.zip", + "reference": null, + "shasum": "891f5f3f5c23dd19e2f815754f4c5e8b4da6af1c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-cron": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-page-cache": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Persistent" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-product-alert", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-product-alert-0.74.0-beta2.zip", + "reference": null, + "shasum": "43292b34d4078dddc73b7bb4bcdb889292b578a6" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/ProductAlert" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-quote", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-quote-0.74.0-beta2.zip", + "reference": null, + "shasum": "8c3033317c359e5fc20e9308ed796ae6760fa22f" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-catalog-rule": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Quote" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-reports", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-reports-0.74.0-beta2.zip", + "reference": null, + "shasum": "38b1c0ddfbd756666cb0bc05e687d01ced515464" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-downloadable": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-review": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-sales-rule": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Reports" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-require-js", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-require-js-0.74.0-beta2.zip", + "reference": null, + "shasum": "6e94b63070fd1c174ab55c109d384d5b3bb04131" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/RequireJs" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-review", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-review-0.74.0-beta2.zip", + "reference": null, + "shasum": "45385eac2767e9165bfd88bcbc6a69dccca29f38" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-newsletter": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-cookie": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Review" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-rss", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-rss-0.74.0-beta2.zip", + "reference": null, + "shasum": "0706c2102a0003e2f214c35b77c1e628519b727a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Rss" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-rule", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-rule-0.74.0-beta2.zip", + "reference": null, + "shasum": "762edf4eabfe082b589b09bcf47a3ef2ac8a7f35" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Rule" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-sales", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-sales-0.74.0-beta2.zip", + "reference": null, + "shasum": "bf591f1f3033aa2fd3fe8bfb2732a6e7b81a499c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-email": "0.74.0-beta2", + "magento/module-gift-message": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-reports": "0.74.0-beta2", + "magento/module-sales-rule": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "magento/module-wishlist": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Sales" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-sales-rule", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-sales-rule-0.74.0-beta2.zip", + "reference": null, + "shasum": "fdb556db9915f50b0bbbc3fd5f004c21fdf144ac" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-rule": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-reports": "0.74.0-beta2", + "magento/module-rule": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/SalesRule" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-search", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-search-0.74.0-beta2.zip", + "reference": null, + "shasum": "a9605f79153be5e6206e269161ec6b0fe27b4b1a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog-search": "0.74.0-beta2", + "magento/module-reports": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Search" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-sendfriend", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-sendfriend-0.74.0-beta2.zip", + "reference": null, + "shasum": "3830393608a4c34ef05eb26de6543ffe3739e1f4" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Sendfriend" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-shipping", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-shipping-0.74.0-beta2.zip", + "reference": null, + "shasum": "0793baf12096494d141c3e6419f53a9c57aef0df" + }, + "require": { + "ext-gd": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-contact": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-payment": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-fedex": "0.74.0-beta2", + "magento/module-ups": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Shipping" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-sitemap", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-sitemap-0.74.0-beta2.zip", + "reference": null, + "shasum": "55e734ce8228a6e0b39eccb1829ae617845f9e53" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-url-rewrite": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Sitemap" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-store", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-store-0.74.0-beta2.zip", + "reference": null, + "shasum": "b53f0f39a5eceb85e0b28bcf32d5bb68314b3027" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Store" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-tax", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-tax-0.74.0-beta2.zip", + "reference": null, + "shasum": "ed22d2db556957de4ab5406d4d0edce811855b9b" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-reports": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Tax" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-tax-import-export", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-tax-import-export-0.74.0-beta2.zip", + "reference": null, + "shasum": "90716decbf4c0eb8fb29523530471fc2f8fbb426" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/TaxImportExport" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-theme", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-theme-0.74.0-beta2.zip", + "reference": null, + "shasum": "4754e71f25664ca7d010351e63c8c017e3c649dd" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-media-storage": "0.74.0-beta2", + "magento/module-require-js": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-widget": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-translation": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Theme" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-translation", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-translation-0.74.0-beta2.zip", + "reference": null, + "shasum": "68e633ff0380bb3d9660c17149935a0cdeda4edb" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-developer": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Translation" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-ui", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-ui-0.74.0-beta2.zip", + "reference": null, + "shasum": "6edb28f5c771ccb9e747ea71922ecbea80a32391" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Ui" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-ups", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-ups-0.74.0-beta2.zip", + "reference": null, + "shasum": "70a8139468124067bfb07bf1c071638484136883" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Ups" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-url-rewrite", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-url-rewrite-0.74.0-beta2.zip", + "reference": null, + "shasum": "8e781525600ff9d43383816283807ebcff07a8bf" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-url-rewrite": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-cms-url-rewrite": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/UrlRewrite" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-user", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-user-0.74.0-beta2.zip", + "reference": null, + "shasum": "023375251bc6104b31984e4509d01907f6661c6b" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-integration": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/User" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-usps", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-usps-0.74.0-beta2.zip", + "reference": null, + "shasum": "22eecac7075ca9c6ecaa08e482b59f5c29ba465c" + }, + "require": { + "lib-libxml": "*", + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-config": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-shipping": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Usps" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-variable", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-variable-0.74.0-beta2.zip", + "reference": null, + "shasum": "73cb1fdc0ff84722f854736a4016e3f5fa331ec1" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-email": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.4.11|~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Variable" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-version", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-version-0.74.0-beta2.zip", + "reference": null, + "shasum": "de77cb875f16d6eec9c785df117eddf469bec459" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Version" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-webapi", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-webapi-0.74.0-beta2.zip", + "reference": null, + "shasum": "c189f264c1bb186e052a2ed375b4b9e00295f3a6" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-authorization": "0.74.0-beta2", + "magento/module-backend": "0.74.0-beta2", + "magento/module-integration": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-user": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Webapi" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-weee", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-weee-0.74.0-beta2.zip", + "reference": null, + "shasum": "cc524f7211673263beb1f4c6b96bf32ffa04527b" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-directory": "0.74.0-beta2", + "magento/module-eav": "0.74.0-beta2", + "magento/module-quote": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-tax": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Weee" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-widget", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-widget-0.74.0-beta2.zip", + "reference": null, + "shasum": "f23668007563d5b7f9122e3eb453359fab9d1249" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-cms": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-variable": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Widget" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/module-wishlist", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_module-wishlist-0.74.0-beta2.zip", + "reference": null, + "shasum": "fca56783501ad2b8f2c583350cb038f8191634e1" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/module-backend": "0.74.0-beta2", + "magento/module-catalog": "0.74.0-beta2", + "magento/module-catalog-inventory": "0.74.0-beta2", + "magento/module-checkout": "0.74.0-beta2", + "magento/module-customer": "0.74.0-beta2", + "magento/module-grouped-product": "0.74.0-beta2", + "magento/module-rss": "0.74.0-beta2", + "magento/module-sales": "0.74.0-beta2", + "magento/module-store": "0.74.0-beta2", + "magento/module-theme": "0.74.0-beta2", + "magento/module-ui": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "suggest": { + "magento/module-bundle": "0.74.0-beta2", + "magento/module-configurable-product": "0.74.0-beta2", + "magento/module-cookie": "0.74.0-beta2", + "magento/module-downloadable": "0.74.0-beta2" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/Wishlist" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/product-community-edition", + "version": "0.74.0-beta2", + "source": { + "type": "git", + "url": "https://github.com/magento/magento2-community-edition.git", + "reference": "830bba759a180a97375275013b974c4606805466" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/magento2-community-edition/zipball/830bba759a180a97375275013b974c4606805466", + "reference": "830bba759a180a97375275013b974c4606805466", + "shasum": "" + }, + "require": { + "composer/composer": "1.0.0-alpha8", + "magento/framework": "self.version", + "magento/language-de_de": "self.version", + "magento/language-en_us": "self.version", + "magento/language-es_es": "self.version", + "magento/language-fr_fr": "self.version", + "magento/language-nl_nl": "self.version", + "magento/language-pt_br": "self.version", + "magento/language-zh_cn": "self.version", + "magento/magento-composer-installer": "*", + "magento/magento2-base": "self.version", + "magento/module-admin-notification": "self.version", + "magento/module-authorization": "self.version", + "magento/module-backend": "self.version", + "magento/module-backup": "self.version", + "magento/module-bundle": "self.version", + "magento/module-captcha": "self.version", + "magento/module-catalog": "self.version", + "magento/module-catalog-import-export": "self.version", + "magento/module-catalog-inventory": "self.version", + "magento/module-catalog-rule": "self.version", + "magento/module-catalog-search": "self.version", + "magento/module-catalog-url-rewrite": "self.version", + "magento/module-catalog-widget": "self.version", + "magento/module-centinel": "self.version", + "magento/module-checkout": "self.version", + "magento/module-checkout-agreements": "self.version", + "magento/module-cms": "self.version", + "magento/module-cms-url-rewrite": "self.version", + "magento/module-config": "self.version", + "magento/module-configurable-import-export": "self.version", + "magento/module-configurable-product": "self.version", + "magento/module-contact": "self.version", + "magento/module-cookie": "self.version", + "magento/module-cron": "self.version", + "magento/module-currency-symbol": "self.version", + "magento/module-customer": "self.version", + "magento/module-customer-import-export": "self.version", + "magento/module-developer": "self.version", + "magento/module-dhl": "self.version", + "magento/module-directory": "self.version", + "magento/module-downloadable": "self.version", + "magento/module-eav": "self.version", + "magento/module-email": "self.version", + "magento/module-fedex": "self.version", + "magento/module-gift-message": "self.version", + "magento/module-google-adwords": "self.version", + "magento/module-google-analytics": "self.version", + "magento/module-google-optimizer": "self.version", + "magento/module-google-shopping": "self.version", + "magento/module-grouped-import-export": "self.version", + "magento/module-grouped-product": "self.version", + "magento/module-import-export": "self.version", + "magento/module-indexer": "self.version", + "magento/module-integration": "self.version", + "magento/module-layered-navigation": "self.version", + "magento/module-media-storage": "self.version", + "magento/module-msrp": "self.version", + "magento/module-multishipping": "self.version", + "magento/module-newsletter": "self.version", + "magento/module-offline-payments": "self.version", + "magento/module-offline-shipping": "self.version", + "magento/module-page-cache": "self.version", + "magento/module-payment": "self.version", + "magento/module-persistent": "self.version", + "magento/module-product-alert": "self.version", + "magento/module-quote": "self.version", + "magento/module-reports": "self.version", + "magento/module-require-js": "self.version", + "magento/module-review": "self.version", + "magento/module-rss": "self.version", + "magento/module-rule": "self.version", + "magento/module-sales": "self.version", + "magento/module-sales-rule": "self.version", + "magento/module-search": "self.version", + "magento/module-sendfriend": "self.version", + "magento/module-shipping": "self.version", + "magento/module-sitemap": "self.version", + "magento/module-store": "self.version", + "magento/module-tax": "self.version", + "magento/module-tax-import-export": "self.version", + "magento/module-theme": "self.version", + "magento/module-translation": "self.version", + "magento/module-ui": "self.version", + "magento/module-ups": "self.version", + "magento/module-url-rewrite": "self.version", + "magento/module-user": "self.version", + "magento/module-usps": "self.version", + "magento/module-variable": "self.version", + "magento/module-version": "self.version", + "magento/module-webapi": "self.version", + "magento/module-weee": "self.version", + "magento/module-widget": "self.version", + "magento/module-wishlist": "self.version", + "magento/theme-adminhtml-backend": "self.version", + "magento/theme-frontend-blank": "self.version", + "magento/theme-frontend-luma": "self.version", + "magento/zendframework1": "1.12.10", + "monolog/monolog": "1.11.0", + "oyejorge/less.php": "1.7.0.3", + "php": "~5.5.0|~5.6.0|~7.0.0", + "tubalmartin/cssmin": "2.4.8-p4", + "zendframework/zend-code": "2.3.1", + "zendframework/zend-config": "2.3.1", + "zendframework/zend-console": "2.3.1", + "zendframework/zend-di": "2.3.1", + "zendframework/zend-eventmanager": "2.3.1", + "zendframework/zend-form": "2.3.1", + "zendframework/zend-http": "2.3.1", + "zendframework/zend-json": "2.3.1", + "zendframework/zend-log": "2.3.1", + "zendframework/zend-modulemanager": "2.3.1", + "zendframework/zend-mvc": "2.3.1", + "zendframework/zend-serializer": "2.3.1", + "zendframework/zend-server": "2.3.1", + "zendframework/zend-servicemanager": "2.3.1", + "zendframework/zend-soap": "2.3.1", + "zendframework/zend-stdlib": "2.3.1", + "zendframework/zend-text": "2.3.1", + "zendframework/zend-uri": "2.3.1", + "zendframework/zend-validator": "2.3.1", + "zendframework/zend-view": "2.3.1" + }, + "require-dev": { + "ext-ctype": "*", + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-intl": "*", + "ext-mcrypt": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "fabpot/php-cs-fixer": "~1.2", + "lib-libxml": "*", + "lusitanian/oauth": "~0.3", + "pdepend/pdepend": "2.0.6", + "phpmd/phpmd": "@stable", + "phpunit/phpunit": "4.1.0", + "sjparkinson/static-review": "~4.1", + "squizlabs/php_codesniffer": "1.5.3" + }, + "type": "project", + "autoload": { + "psr-4": { + "Magento\\Framework\\": "lib/internal/Magento/Framework/", + "Magento\\Setup\\": "setup/src/Magento/Setup/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "eCommerce Platform for Growth (Community Edition)", + "time": "2015-03-27 15:47:02" + }, + { + "name": "magento/sample-module-minimal", + "version": "1.0.0", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_sample-module-minimal-1.0.0.zip", + "reference": null, + "shasum": "8c8bd3ee4907a6a91bffeac177628bd4f238d6e4" + }, + "require": { + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-module", + "extra": { + "map": [ + [ + "*", + "Magento/SampleMinimal" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "A minimal skeleton Magento 2 module" + }, + { + "name": "magento/theme-adminhtml-backend", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_theme-adminhtml-backend-0.74.0-beta2.zip", + "reference": null, + "shasum": "d8bd8d6b70d65d6f38751f7e9f9cbbebf751c97c" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-theme", + "extra": { + "map": [ + [ + "*", + "adminhtml/Magento/backend" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/theme-frontend-blank", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_theme-frontend-blank-0.74.0-beta2.zip", + "reference": null, + "shasum": "e298ae05894e97ba3d94a87b77086dfe0c2d974a" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-theme", + "extra": { + "map": [ + [ + "*", + "frontend/Magento/blank" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/theme-frontend-luma", + "version": "0.74.0-beta2", + "dist": { + "type": "zip", + "url": "https://packages.magento.com/_packages/magento_theme-frontend-luma-0.74.0-beta2.zip", + "reference": null, + "shasum": "82e29e8fd8d928c33f064fb431d06f3968f37327" + }, + "require": { + "magento/framework": "0.74.0-beta2", + "magento/magento-composer-installer": "*", + "magento/theme-frontend-blank": "0.74.0-beta2", + "php": "~5.5.0|~5.6.0" + }, + "type": "magento2-theme", + "extra": { + "map": [ + [ + "*", + "frontend/Magento/luma" + ] + ] + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "N/A" + }, + { + "name": "magento/zendframework1", + "version": "1.12.10", + "source": { + "type": "git", + "url": "https://github.com/magento/zf1.git", + "reference": "d1e5cd8c9f83229bcdd9bb485c3ce25259c77884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/zf1/zipball/d1e5cd8c9f83229bcdd9bb485c3ce25259c77884", + "reference": "d1e5cd8c9f83229bcdd9bb485c3ce25259c77884", + "shasum": "" + }, + "require": { + "php": ">=5.2.11" + }, + "require-dev": { + "phpunit/dbunit": "1.3.*", + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12.x-dev" + } + }, + "autoload": { + "psr-0": { + "Zend_": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "library/" + ], + "license": [ + "BSD-3-Clause" + ], + "description": "Magento Zend Framework 1", + "homepage": "http://framework.zend.com/", + "keywords": [ + "ZF1", + "framework" + ], + "time": "2015-02-06 17:25:45" + }, + { + "name": "monolog/monolog", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", + "reference": "ec3961874c43840e96da3a8a1ed20d8c73d7e5aa", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.4, >2.4.8", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "phpunit/phpunit": "~3.7.0", + "raven/raven": "~0.5", + "ruflin/elastica": "0.90.*", + "videlalvaro/php-amqplib": "~2.4" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2014-09-30 13:30:58" + }, + { + "name": "oyejorge/less.php", + "version": "v1.7.0.3", + "source": { + "type": "git", + "url": "https://github.com/oyejorge/less.php.git", + "reference": "6e08ecb07e6f6d9170c23e8744c58fdd822ad0de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/oyejorge/less.php/zipball/6e08ecb07e6f6d9170c23e8744c58fdd822ad0de", + "reference": "6e08ecb07e6f6d9170c23e8744c58fdd822ad0de", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "bin": [ + "bin/lessc" + ], + "type": "library", + "autoload": { + "psr-0": { + "Less": "lib/" + }, + "classmap": [ + "lessc.inc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Matt Agar", + "homepage": "https://github.com/agar" + }, + { + "name": "Martin Jantošovič", + "homepage": "https://github.com/Mordred" + }, + { + "name": "Josh Schmidt", + "homepage": "https://github.com/oyejorge" + } + ], + "description": "PHP port of the Javascript version of LESS http://lesscss.org", + "homepage": "http://lessphp.gpeasy.com", + "keywords": [ + "css", + "less", + "less.js", + "lesscss", + "php", + "stylesheet" + ], + "time": "2015-03-10 18:12:59" + }, + { + "name": "psr/log", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "bf2c13de4300e227d7b2fd08027673a79c519987" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/bf2c13de4300e227d7b2fd08027673a79c519987", + "reference": "bf2c13de4300e227d7b2fd08027673a79c519987", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2015-03-26 14:39:45" + }, + { + "name": "seld/jsonlint", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "863ae85c6d3ef60ca49cb12bd051c4a0648c40c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/863ae85c6d3ef60ca49cb12bd051c4a0648c40c4", + "reference": "863ae85c6d3ef60ca49cb12bd051c4a0648c40c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2015-01-04 21:18:15" + }, + { + "name": "symfony/console", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "7e857a2b52b5833ed27d78a6b1b846bd440ee8bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/7e857a2b52b5833ed27d78a6b1b846bd440ee8bd", + "reference": "7e857a2b52b5833ed27d78a6b1b846bd440ee8bd", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2015-05-15 14:11:12" + }, + { + "name": "symfony/finder", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder.git", + "reference": "fd26bdbb67bc8753884eff5767a4f1ee90e2284b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Finder/zipball/fd26bdbb67bc8753884eff5767a4f1ee90e2284b", + "reference": "fd26bdbb67bc8753884eff5767a4f1ee90e2284b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2015-05-15 14:11:12" + }, + { + "name": "symfony/process", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "daf96f1491cf0b8d8de60e6d3b5fda8e502b3798" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/daf96f1491cf0b8d8de60e6d3b5fda8e502b3798", + "reference": "daf96f1491cf0b8d8de60e6d3b5fda8e502b3798", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2015-05-15 14:11:12" + }, + { + "name": "tubalmartin/cssmin", + "version": "v2.4.8-p4", + "source": { + "type": "git", + "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", + "reference": "fe84d71e8420243544c0ce3bd0f5d7c1936b0f90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/fe84d71e8420243544c0ce3bd0f5d7c1936b0f90", + "reference": "fe84d71e8420243544c0ce3bd0f5d7c1936b0f90", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "cssmin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Túbal Martín", + "homepage": "http://tubalmartin.me/" + } + ], + "description": "A PHP port of the YUI CSS compressor", + "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", + "keywords": [ + "compress", + "compressor", + "css", + "minify", + "yui" + ], + "time": "2014-09-22 08:08:50" + }, + { + "name": "zendframework/zend-code", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "a64e90c9ee8c939335ee7e21e39e3342c0e54526" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/a64e90c9ee8c939335ee7e21e39e3342c0e54526", + "reference": "a64e90c9ee8c939335ee7e21e39e3342c0e54526", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "self.version" + }, + "require-dev": { + "doctrine/common": ">=2.1", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-stdlib": "self.version" + }, + "suggest": { + "doctrine/common": "Doctrine\\Common >=2.1 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2014-04-15 14:47:18" + }, + { + "name": "zendframework/zend-config", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-config.git", + "reference": "698c139707380b29fd09791ec1c21b837e9a3f15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-config/zipball/698c139707380b29fd09791ec1c21b837e9a3f15", + "reference": "698c139707380b29fd09791ec1c21b837e9a3f15", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-filter": "self.version", + "zendframework/zend-i18n": "self.version", + "zendframework/zend-json": "self.version", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-filter": "Zend\\Filter component", + "zendframework/zend-i18n": "Zend\\I18n component", + "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", + "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Config\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a nested object property based user interface for accessing this configuration data within application code", + "homepage": "https://github.com/zendframework/zend-config", + "keywords": [ + "config", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-console", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-console.git", + "reference": "4253efd75a022d97ef326eac38a06c8eebb48a37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-console/zipball/4253efd75a022d97ef326eac38a06c8eebb48a37", + "reference": "4253efd75a022d97ef326eac38a06c8eebb48a37", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "suggest": { + "zendframework/zend-filter": "To support DefaultRouteMatcher usage", + "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Console\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-console", + "keywords": [ + "console", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-di", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-di.git", + "reference": "7502db10f8023bfd4e860ce83c9cdeda0db42c31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-di/zipball/7502db10f8023bfd4e860ce83c9cdeda0db42c31", + "reference": "7502db10f8023bfd4e860ce83c9cdeda0db42c31", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-code": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Di\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-di", + "keywords": [ + "di", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-escaper", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-escaper.git", + "reference": "2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1", + "reference": "2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-escaper", + "keywords": [ + "escaper", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "ec158e89d0290f988a29ccd6bf179b561efbb702" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/ec158e89d0290f988a29ccd6bf179b561efbb702", + "reference": "ec158e89d0290f988a29ccd6bf179b561efbb702", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-event-manager", + "keywords": [ + "eventmanager", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-filter", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-filter.git", + "reference": "307fe694659e08ffd710c70e4db8bc60187bcc84" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/307fe694659e08ffd710c70e4db8bc60187bcc84", + "reference": "307fe694659e08ffd710c70e4db8bc60187bcc84", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-crypt": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-uri": "self.version" + }, + "suggest": { + "zendframework/zend-crypt": "Zend\\Crypt component", + "zendframework/zend-i18n": "Zend\\I18n component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component", + "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Filter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a set of commonly needed data filters", + "homepage": "https://github.com/zendframework/zend-filter", + "keywords": [ + "filter", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-form", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-form.git", + "reference": "d7a1f5bc4626b1df990391502a868b28c37ba65d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-form/zipball/d7a1f5bc4626b1df990391502a868b28c37ba65d", + "reference": "d7a1f5bc4626b1df990391502a868b28c37ba65d", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-inputfilter": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-captcha": "self.version", + "zendframework/zend-code": "self.version", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-filter": "self.version", + "zendframework/zend-i18n": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-validator": "self.version", + "zendframework/zend-view": "self.version", + "zendframework/zendservice-recaptcha": "*" + }, + "suggest": { + "zendframework/zend-captcha": "Zend\\Captcha component", + "zendframework/zend-code": "Zend\\Code component", + "zendframework/zend-eventmanager": "Zend\\EventManager component", + "zendframework/zend-filter": "Zend\\Filter component", + "zendframework/zend-i18n": "Zend\\I18n component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component", + "zendframework/zend-validator": "Zend\\Validator component", + "zendframework/zend-view": "Zend\\View component", + "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Form\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-form", + "keywords": [ + "form", + "zf2" + ], + "time": "2014-04-15 14:36:41" + }, + { + "name": "zendframework/zend-http", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-http.git", + "reference": "869ce7bdf60727e14d85c305d2948fbe831c3534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/869ce7bdf60727e14d85c305d2948fbe831c3534", + "reference": "869ce7bdf60727e14d85c305d2948fbe831c3534", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-loader": "self.version", + "zendframework/zend-stdlib": "self.version", + "zendframework/zend-uri": "self.version", + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://github.com/zendframework/zend-http", + "keywords": [ + "http", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-inputfilter", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-inputfilter.git", + "reference": "abca740015a856d03542f5b6c535b8874f84b622" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/abca740015a856d03542f5b6c535b8874f84b622", + "reference": "abca740015a856d03542f5b6c535b8874f84b622", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-filter": "self.version", + "zendframework/zend-stdlib": "self.version", + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-servicemanager": "To support plugin manager support" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\InputFilter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-input-filter", + "keywords": [ + "inputfilter", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-json", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-json.git", + "reference": "5284687fc3aeab27961d2e17ada08973ae6daafe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-json/zipball/5284687fc3aeab27961d2e17ada08973ae6daafe", + "reference": "5284687fc3aeab27961d2e17ada08973ae6daafe", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-http": "self.version", + "zendframework/zend-server": "self.version" + }, + "suggest": { + "zendframework/zend-http": "Zend\\Http component", + "zendframework/zend-server": "Zend\\Server component", + "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Json\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", + "homepage": "https://github.com/zendframework/zend-json", + "keywords": [ + "json", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-loader", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-loader.git", + "reference": "5e0bd7e28c644078685f525cf8ae03d9a01ae292" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/5e0bd7e28c644078685f525cf8ae03d9a01ae292", + "reference": "5e0bd7e28c644078685f525cf8ae03d9a01ae292", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-loader", + "keywords": [ + "loader", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-log", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-log.git", + "reference": "217611433f5cb56d4420a1db8f164e5db85d815d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-log/zipball/217611433f5cb56d4420a1db8f164e5db85d815d", + "reference": "217611433f5cb56d4420a1db8f164e5db85d815d", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-console": "self.version", + "zendframework/zend-db": "self.version", + "zendframework/zend-escaper": "self.version", + "zendframework/zend-mail": "self.version", + "zendframework/zend-validator": "self.version" + }, + "suggest": { + "ext-mongo": "*", + "zendframework/zend-console": "Zend\\Console component", + "zendframework/zend-db": "Zend\\Db component", + "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML formatter", + "zendframework/zend-mail": "Zend\\Mail component", + "zendframework/zend-validator": "Zend\\Validator component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Log\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "component for general purpose logging", + "homepage": "https://github.com/zendframework/zend-log", + "keywords": [ + "log", + "logging", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-math", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-math.git", + "reference": "63225fcebb196fc6e20094f5f01e9354779ec31e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-math/zipball/63225fcebb196fc6e20094f5f01e9354779ec31e", + "reference": "63225fcebb196fc6e20094f5f01e9354779ec31e", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "suggest": { + "ext-bcmath": "If using the bcmath functionality", + "ext-gmp": "If using the gmp functionality", + "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", + "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-math", + "keywords": [ + "math", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-modulemanager", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-modulemanager.git", + "reference": "d4591b958e40b8f5ae8110d9b203331437aa19f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/d4591b958e40b8f5ae8110d9b203331437aa19f2", + "reference": "d4591b958e40b8f5ae8110d9b203331437aa19f2", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-config": "self.version", + "zendframework/zend-console": "self.version", + "zendframework/zend-loader": "self.version", + "zendframework/zend-mvc": "self.version", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-config": "Zend\\Config component", + "zendframework/zend-console": "Zend\\Console component", + "zendframework/zend-loader": "Zend\\Loader component", + "zendframework/zend-mvc": "Zend\\Mvc component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\ModuleManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-module-manager", + "keywords": [ + "modulemanager", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-mvc", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-mvc.git", + "reference": "ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff", + "reference": "ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-form": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-authentication": "self.version", + "zendframework/zend-console": "self.version", + "zendframework/zend-di": "self.version", + "zendframework/zend-filter": "self.version", + "zendframework/zend-form": "self.version", + "zendframework/zend-http": "self.version", + "zendframework/zend-i18n": "self.version", + "zendframework/zend-inputfilter": "self.version", + "zendframework/zend-json": "self.version", + "zendframework/zend-log": "self.version", + "zendframework/zend-modulemanager": "self.version", + "zendframework/zend-serializer": "self.version", + "zendframework/zend-session": "self.version", + "zendframework/zend-text": "self.version", + "zendframework/zend-uri": "self.version", + "zendframework/zend-validator": "self.version", + "zendframework/zend-version": "self.version", + "zendframework/zend-view": "self.version" + }, + "suggest": { + "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", + "zendframework/zend-config": "Zend\\Config component", + "zendframework/zend-console": "Zend\\Console component", + "zendframework/zend-di": "Zend\\Di component", + "zendframework/zend-filter": "Zend\\Filter component", + "zendframework/zend-form": "Zend\\Form component", + "zendframework/zend-http": "Zend\\Http component", + "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", + "zendframework/zend-inputfilter": "Zend\\Inputfilter component", + "zendframework/zend-json": "Zend\\Json component", + "zendframework/zend-log": "Zend\\Log component", + "zendframework/zend-modulemanager": "Zend\\ModuleManager component", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", + "zendframework/zend-stdlib": "Zend\\Stdlib component", + "zendframework/zend-text": "Zend\\Text component", + "zendframework/zend-uri": "Zend\\Uri component", + "zendframework/zend-validator": "Zend\\Validator component", + "zendframework/zend-version": "Zend\\Version component", + "zendframework/zend-view": "Zend\\View component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Mvc\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-mvc", + "keywords": [ + "mvc", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-serializer", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-serializer.git", + "reference": "22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6", + "reference": "22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-json": "self.version", + "zendframework/zend-math": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-servicemanager": "To support plugin manager support" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Serializer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover", + "homepage": "https://github.com/zendframework/zend-serializer", + "keywords": [ + "serializer", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-server", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-server.git", + "reference": "bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-server/zipball/bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c", + "reference": "bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-code": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-server", + "keywords": [ + "server", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-servicemanager", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-servicemanager.git", + "reference": "7a428b595a1fcd4c2a8026ee5d5f89a56036f682" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/7a428b595a1fcd4c2a8026ee5d5f89a56036f682", + "reference": "7a428b595a1fcd4c2a8026ee5d5f89a56036f682", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-di": "self.version" + }, + "suggest": { + "zendframework/zend-di": "Zend\\Di component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-service-manager", + "keywords": [ + "servicemanager", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-soap", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-soap.git", + "reference": "fab9f5309be04cacf01af54f694aed5102592c5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/fab9f5309be04cacf01af54f694aed5102592c5c", + "reference": "fab9f5309be04cacf01af54f694aed5102592c5c", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-server": "self.version", + "zendframework/zend-stdlib": "self.version", + "zendframework/zend-uri": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-http": "self.version" + }, + "suggest": { + "zendframework/zend-http": "Zend\\Http component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Soap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-soap", + "keywords": [ + "soap", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-stdlib", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "6079302963d57f36a9ba92ed3f38b992997aa78d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/6079302963d57f36a9ba92ed3f38b992997aa78d", + "reference": "6079302963d57f36a9ba92ed3f38b992997aa78d", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-filter": "self.version", + "zendframework/zend-serializer": "self.version", + "zendframework/zend-servicemanager": "self.version" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-filter": "To support naming strategy hydrator usage", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2014-04-15 13:59:53" + }, + { + "name": "zendframework/zend-text", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-text.git", + "reference": "e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-text/zipball/e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90", + "reference": "e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Text\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-text", + "keywords": [ + "text", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-uri", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-uri.git", + "reference": "0de6ba8c166a235588783ff8e88a19b364630d89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/0de6ba8c166a235588783ff8e88a19b364630d89", + "reference": "0de6ba8c166a235588783ff8e88a19b364630d89", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-escaper": "self.version", + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "a component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://github.com/zendframework/zend-uri", + "keywords": [ + "uri", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-validator", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-validator.git", + "reference": "a5f97f6c3a27a27b1235f724ad0048715459f013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/a5f97f6c3a27a27b1235f724ad0048715459f013", + "reference": "a5f97f6c3a27a27b1235f724ad0048715459f013", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-db": "self.version", + "zendframework/zend-filter": "self.version", + "zendframework/zend-i18n": "self.version", + "zendframework/zend-math": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-session": "self.version", + "zendframework/zend-uri": "self.version" + }, + "suggest": { + "zendframework/zend-db": "Zend\\Db component", + "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", + "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages as well as to use the various Date validators", + "zendframework/zend-math": "Zend\\Math component", + "zendframework/zend-resources": "Translations of validator messages", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "zendframework/zend-session": "Zend\\Session component", + "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a set of commonly needed validators", + "homepage": "https://github.com/zendframework/zend-validator", + "keywords": [ + "validator", + "zf2" + ], + "time": "2014-04-14 19:50:30" + }, + { + "name": "zendframework/zend-view", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-view.git", + "reference": "e308d498fa7851f26bd639bfe3ebbfba397c47bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-view/zipball/e308d498fa7851f26bd639bfe3ebbfba397c47bc", + "reference": "e308d498fa7851f26bd639bfe3ebbfba397c47bc", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "self.version", + "zendframework/zend-loader": "self.version", + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "dev-master", + "zendframework/zend-authentication": "self.version", + "zendframework/zend-escaper": "self.version", + "zendframework/zend-feed": "self.version", + "zendframework/zend-filter": "self.version", + "zendframework/zend-http": "self.version", + "zendframework/zend-i18n": "self.version", + "zendframework/zend-json": "self.version", + "zendframework/zend-mvc": "self.version", + "zendframework/zend-navigation": "self.version", + "zendframework/zend-paginator": "self.version", + "zendframework/zend-permissions-acl": "self.version", + "zendframework/zend-servicemanager": "self.version", + "zendframework/zend-uri": "self.version" + }, + "suggest": { + "zendframework/zend-authentication": "Zend\\Authentication component", + "zendframework/zend-escaper": "Zend\\Escaper component", + "zendframework/zend-feed": "Zend\\Feed component", + "zendframework/zend-filter": "Zend\\Filter component", + "zendframework/zend-http": "Zend\\Http component", + "zendframework/zend-i18n": "Zend\\I18n component", + "zendframework/zend-json": "Zend\\Json component", + "zendframework/zend-mvc": "Zend\\Mvc component", + "zendframework/zend-navigation": "Zend\\Navigation component", + "zendframework/zend-paginator": "Zend\\Paginator component", + "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component", + "zendframework/zend-uri": "Zend\\Uri component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev", + "dev-develop": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\View\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides a system of helpers, output filters, and variable escaping", + "homepage": "https://github.com/zendframework/zend-view", + "keywords": [ + "view", + "zf2" + ], + "time": "2014-04-15 14:36:41" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/GridTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/GridTest.php new file mode 100644 index 0000000000000..766d95a7a1bb2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/GridTest.php @@ -0,0 +1,35 @@ +dispatch('backend/admin/locks/grid'); + + $body = $this->getResponse()->getBody(); + $this->assertContains('data-column="username"', $body); + $this->assertContains('data-column="last_login"', $body); + $this->assertContains('data-column="last_login"', $body); + $this->assertContains('data-column="failures_num"', $body); + $this->assertContains('data-column="lock_expires"', $body); + $this->assertRegExp( + '/\s*adminUser1\s*<\/td>/', + $body + ); + $this->assertRegExp( + '/\s*adminUser2\s*<\/td>/', + $body + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/IndexTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/IndexTest.php new file mode 100644 index 0000000000000..c11802cc69c5b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/IndexTest.php @@ -0,0 +1,31 @@ +dispatch('backend/admin/locks/index'); + + $body = $this->getResponse()->getBody(); + $this->assertContains('

Locked Users

', $body); + $this->assertRegExp( + '/\s*adminUser1\s*<\/td>/', + $body + ); + $this->assertRegExp( + '/\s*adminUser2\s*<\/td>/', + $body + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/MassUnlockTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/MassUnlockTest.php new file mode 100644 index 0000000000000..a0bb3e09004d5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/Locks/MassUnlockTest.php @@ -0,0 +1,38 @@ +create('Magento\User\Model\User'); + $userIds[] = $model->loadByUsername('adminUser1')->getId(); + $userIds[] = $model->loadByUsername('adminUser2')->getId(); + + $request = $this->getRequest(); + $request->setPostValue( + 'unlock', + $userIds + ); + $this->dispatch('backend/admin/locks/massunlock'); + + $this->assertSessionMessages( + $this->contains((string)__('Unlocked %1 user(s).', count($userIds))), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/User/_files/locked_users.php b/dev/tests/integration/testsuite/Magento/User/_files/locked_users.php new file mode 100644 index 0000000000000..51a2f82c36873 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/User/_files/locked_users.php @@ -0,0 +1,51 @@ +create('Magento\User\Model\User'); +$model->setFirstname("John") + ->setLastname("Doe") + ->setUsername('adminUser1') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUser1@example.com') + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow'); +$model->save(); +$userIds[] = $model->getDataByKey('user_id'); + +/** @var $model \Magento\User\Model\User */ +$model = $objectManager->create('Magento\User\Model\User'); +$model->setFirstname("John") + ->setLastname("Doe") + ->setUsername('adminUser2') + ->setPassword(\Magento\TestFramework\Bootstrap::ADMIN_PASSWORD) + ->setEmail('adminUser2@example.com') + ->setRoleType('G') + ->setResourceId('Magento_Adminhtml::all') + ->setPrivileges("") + ->setAssertId(0) + ->setRoleId(1) + ->setPermission('allow'); +$model->save(); +$userIds[] = $model->getDataByKey('user_id'); + +$defaultAdminUserId = 1; +$lockLifetime = 86400; + +/** @var $modelLockedUsers \Magento\User\Model\Resource\User */ +$modelLockedUsers = $objectManager->create('Magento\User\Model\Resource\User'); +$modelLockedUsers->lock($userIds, $defaultAdminUserId, $lockLifetime); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php index 58aafb9b588fa..c756aa6b43d76 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php @@ -7,7 +7,7 @@ 'admin_assert' => 'Magento\Backend', 'authorization_role' => 'Magento\Authorization', 'authorization_rule' => 'Magento\Authorization', - 'admin_user' => 'Magento\Backend', + 'admin_user' => 'Magento\User', 'adminnotification_inbox' => 'Magento\AdminNotification', 'catalog_category_entity_datetime' => 'Magento\Catalog', 'catalog_category_entity_decimal' => 'Magento\Catalog', @@ -193,6 +193,7 @@ 'oauth_nonce' => 'Magento\Integration', 'oauth_token' => 'Magento\Integration', 'authorizenet_debug' => 'Magento\Authorizenet', + 'admin_passwords' => 'Magento\User', 'paypal_cert' => 'Magento\Paypal', 'paypal_payment_transaction' => 'Magento\Paypal', 'paypal_settlement_report' => 'Magento\Paypal', diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/connection/blacklist/files_list.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/connection/blacklist/files_list.php index 1e9233cee49d8..7b4ddc53272ae 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/connection/blacklist/files_list.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/connection/blacklist/files_list.php @@ -12,5 +12,7 @@ '/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php', '/app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php', '/app/code/Magento/CatalogImportExport/Model/Export/Product.php', + '/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php', + '/app/code/Magento/EncryptionKey/Model/Resource/Key/Change.php', //example '/app/code/Magento/Backend/Model/View.php', ]; diff --git a/lib/internal/Magento/Framework/Composer/ComposerInformation.php b/lib/internal/Magento/Framework/Composer/ComposerInformation.php index 39d87457b90e6..40af109990d5b 100755 --- a/lib/internal/Magento/Framework/Composer/ComposerInformation.php +++ b/lib/internal/Magento/Framework/Composer/ComposerInformation.php @@ -6,17 +6,11 @@ namespace Magento\Framework\Composer; -use Composer\Factory as ComposerFactory; use Composer\Package\Link; use Composer\Package\CompletePackageInterface; -use Composer\Package\Version\VersionParser; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem; -use Magento\Framework\Stdlib\DateTime; /** * Class ComposerInformation uses Composer to determine dependency information. - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ComposerInformation { @@ -79,21 +73,6 @@ class ComposerInformation */ private $locker; - /** - * @var \Magento\Framework\Filesystem\Directory\Write - */ - private $directory; - - /** - * @var \Magento\Framework\Stdlib\DateTime - */ - private $dateTime; - - /** - * @var string - */ - private $pathToCacheFile = 'update_composer_packages.json'; - /** @var array */ private static $packageTypes = [ self::THEME_PACKAGE_TYPE, @@ -108,20 +87,14 @@ class ComposerInformation * Constructor * * @param MagentoComposerApplicationFactory $applicationFactory - * @param \Magento\Framework\Filesystem $filesystem - * @param \Magento\Framework\Stdlib\DateTime $dateTime * @throws \Exception */ public function __construct( - MagentoComposerApplicationFactory $applicationFactory, - Filesystem $filesystem, - DateTime $dateTime + MagentoComposerApplicationFactory $applicationFactory ) { $this->application = $applicationFactory->create(); $this->composer = $this->application->createComposer(); $this->locker = $this->composer->getLocker(); - $this->directory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); - $this->dateTime = $dateTime; } /** @@ -238,53 +211,6 @@ public function getInstalledMagentoPackages() return $packages; } - /** - * Sync and cache list of available for update versions for packages - * - * @return bool - */ - public function syncPackagesForUpdate() - { - $availableVersions = []; - foreach ($this->getInstalledMagentoPackages() as $package) { - $latestProductVersion = $this->getLatestNonDevVersion($package['name']); - if ($latestProductVersion && version_compare($latestProductVersion, $package['version'], '>')) { - $packageName = $package['name']; - $availableVersions[$packageName] = [ - 'name' => $packageName, - 'latestVersion' => $latestProductVersion - ]; - } - } - return $this->savePackagesForUpdateToCache($availableVersions) ? true : false; - } - - /** - * Sync and cache list of available for update versions for packages - * - * @return bool|array - */ - public function getPackagesForUpdate() - { - $actualUpdatePackages = []; - $updatePackagesInfo = $this->loadPackagesForUpdateFromCache(); - if (!$updatePackagesInfo) { - return false; - } - $updatePackages = $updatePackagesInfo['packages']; - $availablePackages = $this->getInstalledMagentoPackages(); - foreach ($updatePackages as $package) { - $packageName = $package['name']; - if (array_key_exists($packageName, $availablePackages)) { - if (version_compare($availablePackages[$packageName]['version'], $package['latestVersion'], '<')) { - $actualUpdatePackages[$packageName] = $package; - } - } - } - $updatePackagesInfo['packages'] = $actualUpdatePackages; - return $updatePackagesInfo; - } - /** * Checks if the passed packaged is system package * @@ -299,50 +225,6 @@ public function isSystemPackage($packageName = '') return false; } - /** - * Retrieve the latest available stable version for a package - * - * @param string $package - * @return string - */ - private function getLatestNonDevVersion($package) - { - $versionParser = new VersionParser(); - foreach ($this->getPackageAvailableVersions($package) as $version) { - if ($versionParser->parseStability($version) != 'dev') { - return $version; - } - } - return ''; - } - - /** - * Retrieve all available versions for a package - * - * @param string $package - * @return array - * @throws \RuntimeException - */ - private function getPackageAvailableVersions($package) - { - $versionsPattern = '/^versions\s*\:\s(.+)$/m'; - - $commandParams = [ - self::PARAM_COMMAND => self::COMPOSER_SHOW, - self::PARAM_PACKAGE => $package, - self::PARAM_AVAILABLE => true - ]; - $result = $this->application->runComposerCommand($commandParams); - $matches = []; - preg_match($versionsPattern, $result, $matches); - if (!isset($matches[1])) { - throw new \RuntimeException( - sprintf('Couldn\'t get available versions for package %s', $commandParams[self::PARAM_PACKAGE]) - ); - } - return explode(', ', $matches[1]); - } - /** * Determines if Magento is the root package or it is included as a requirement. * @@ -355,43 +237,6 @@ private function isMagentoRoot() return preg_match('/magento\/magento2.e/', $rootPackage->getName()); } - /** - * Save composer packages available for update to cache - * - * @param array $availableVersions - * @return bool|string - */ - private function savePackagesForUpdateToCache($availableVersions) - { - $syncInfo = []; - $syncInfo['lastSyncDate'] = str_replace('-', '/', $this->dateTime->formatDate(true)); - $syncInfo['packages'] = $availableVersions; - $data = json_encode($syncInfo, JSON_UNESCAPED_SLASHES); - try { - $this->directory->writeFile($this->pathToCacheFile, $data); - } catch (\Magento\Framework\Exception\FileSystemException $e) { - return false; - } - return $data; - } - - /** - * Load composer packages available for update from cache - * - * @return bool|string - */ - private function loadPackagesForUpdateFromCache() - { - if ($this->directory->isExist($this->pathToCacheFile) && $this->directory->isReadable($this->pathToCacheFile)) { - try { - $data = $this->directory->readFile($this->pathToCacheFile); - return json_decode($data, true); - } catch (\Magento\Framework\Exception\FileSystemException $e) { - } - } - return false; - } - /** * Check if a package is inside the root composer or not * diff --git a/lib/internal/Magento/Framework/Encryption/Encryptor.php b/lib/internal/Magento/Framework/Encryption/Encryptor.php index 9a5204b02f17d..cc8c28feae760 100644 --- a/lib/internal/Magento/Framework/Encryption/Encryptor.php +++ b/lib/internal/Magento/Framework/Encryption/Encryptor.php @@ -149,14 +149,27 @@ public function hash($data, $version = self::HASH_VERSION_LATEST) * @param string $password * @param string $hash * @return bool + * @deprecated */ public function validateHash($password, $hash) { - return $this->validateHashByVersion( + return $this->isValidHash($password, $hash); + } + + /** + * Validate hash against hashing method (with or without salt) + * + * @param string $password + * @param string $hash + * @return bool + */ + public function isValidHash($password, $hash) + { + return $this->isValidHashByVersion( $password, $hash, self::HASH_VERSION_SHA256 - ) || $this->validateHashByVersion( + ) || $this->isValidHashByVersion( $password, $hash, self::HASH_VERSION_MD5 @@ -171,7 +184,7 @@ public function validateHash($password, $hash) * @param int $version * @return bool */ - public function validateHashByVersion($password, $hash, $version = self::HASH_VERSION_LATEST) + public function isValidHashByVersion($password, $hash, $version) { // look for salt $hashArr = explode(':', $hash, 2); diff --git a/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php b/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php index b9d98dc2e845b..1e72deaed4502 100644 --- a/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php +++ b/lib/internal/Magento/Framework/Encryption/EncryptorInterface.php @@ -40,9 +40,30 @@ public function hash($data); * @param string $hash * @return bool * @throws \Exception + * @deprecated */ public function validateHash($password, $hash); + /** + * Validate hash against hashing method (with or without salt) + * + * @param string $password + * @param string $hash + * @return bool + * @throws \Exception + */ + public function isValidHash($password, $hash); + + /** + * Validate hash by specified version + * + * @param string $password + * @param string $hash + * @param int $version + * @return bool + */ + public function isValidHashByVersion($password, $hash, $version); + /** * Encrypt a string * diff --git a/setup/src/Magento/Setup/Controller/ComponentGrid.php b/setup/src/Magento/Setup/Controller/ComponentGrid.php index 426a096951e39..e94f06c941833 100644 --- a/setup/src/Magento/Setup/Controller/ComponentGrid.php +++ b/setup/src/Magento/Setup/Controller/ComponentGrid.php @@ -12,6 +12,7 @@ use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\JsonModel; use Zend\View\Model\ViewModel; +use Magento\Setup\Model\UpdatePackagesCache; /** * Controller for component grid tasks @@ -30,17 +31,25 @@ class ComponentGrid extends AbstractActionController */ private $packageInfo; + /** + * @var UpdatePackagesCache + */ + private $updatePackagesCache; + /** * @param ComposerInformation $composerInformation * @param ObjectManagerProvider $objectManagerProvider + * @param UpdatePackagesCache $updatePackagesCache */ public function __construct( ComposerInformation $composerInformation, - ObjectManagerProvider $objectManagerProvider + ObjectManagerProvider $objectManagerProvider, + UpdatePackagesCache $updatePackagesCache ) { $this->composerInformation = $composerInformation; $this->packageInfo = $objectManagerProvider->get() ->get('Magento\Framework\Module\PackageInfoFactory')->create(); + $this->updatePackagesCache = $updatePackagesCache; } /** @@ -63,7 +72,7 @@ public function indexAction() */ public function componentsAction() { - $lastSyncData = $this->composerInformation->getPackagesForUpdate(); + $lastSyncData = $this->updatePackagesCache->getPackagesForUpdate(); $components = $this->composerInformation->getInstalledMagentoPackages(); foreach ($components as $component) { $components[$component['name']]['update'] = false; @@ -103,8 +112,8 @@ public function componentsAction() */ public function syncAction() { - $this->composerInformation->syncPackagesForUpdate(); - $lastSyncData = $this->composerInformation->getPackagesForUpdate(); + $this->updatePackagesCache->syncPackagesForUpdate(); + $lastSyncData = $this->updatePackagesCache->getPackagesForUpdate(); return new JsonModel( [ 'success' => true, diff --git a/setup/src/Magento/Setup/Model/UpdatePackagesCache.php b/setup/src/Magento/Setup/Model/UpdatePackagesCache.php new file mode 100644 index 0000000000000..2d7f68694a47f --- /dev/null +++ b/setup/src/Magento/Setup/Model/UpdatePackagesCache.php @@ -0,0 +1,203 @@ +application = $applicationFactory->create(); + $this->directory = $filesystem->getDirectoryWrite(DirectoryList::VAR_DIR); + $this->dateTime = $dateTime; + $this->composerInformation = $composerInformation; + } + + /** + * Sync and cache list of available for update versions for packages + * + * @return bool + */ + public function syncPackagesForUpdate() + { + $availableVersions = []; + foreach ($this->composerInformation->getInstalledMagentoPackages() as $package) { + $latestProductVersion = $this->getLatestNonDevVersion($package['name']); + if ($latestProductVersion && version_compare($latestProductVersion, $package['version'], '>')) { + $packageName = $package['name']; + $availableVersions[$packageName] = [ + 'name' => $packageName, + 'latestVersion' => $latestProductVersion + ]; + } + } + return $this->savePackagesForUpdateToCache($availableVersions) ? true : false; + } + + /** + * Sync and cache list of available for update versions for packages + * + * @return bool|array + */ + public function getPackagesForUpdate() + { + $actualUpdatePackages = []; + $updatePackagesInfo = $this->loadPackagesForUpdateFromCache(); + if (!$updatePackagesInfo) { + return false; + } + $updatePackages = $updatePackagesInfo['packages']; + $availablePackages = $this->composerInformation->getInstalledMagentoPackages(); + foreach ($updatePackages as $package) { + $packageName = $package['name']; + if (array_key_exists($packageName, $availablePackages)) { + if (version_compare($availablePackages[$packageName]['version'], $package['latestVersion'], '<')) { + $actualUpdatePackages[$packageName] = $package; + } + } + } + $updatePackagesInfo['packages'] = $actualUpdatePackages; + return $updatePackagesInfo; + } + + /** + * Retrieve the latest available stable version for a package + * + * @param string $package + * @return string + */ + private function getLatestNonDevVersion($package) + { + $versionParser = new VersionParser(); + foreach ($this->getPackageAvailableVersions($package) as $version) { + if ($versionParser->parseStability($version) != 'dev') { + return $version; + } + } + return ''; + } + + /** + * Retrieve all available versions for a package + * + * @param string $package + * @return array + * @throws \RuntimeException + */ + private function getPackageAvailableVersions($package) + { + $versionsPattern = '/^versions\s*\:\s(.+)$/m'; + + $commandParams = [ + self::PARAM_COMMAND => self::COMPOSER_SHOW, + self::PARAM_PACKAGE => $package, + self::PARAM_AVAILABLE => true + ]; + $result = $this->application->runComposerCommand($commandParams); + $matches = []; + preg_match($versionsPattern, $result, $matches); + if (!isset($matches[1])) { + throw new \RuntimeException( + sprintf('Couldn\'t get available versions for package %s', $commandParams[self::PARAM_PACKAGE]) + ); + } + return explode(', ', $matches[1]); + } + + /** + * Save composer packages available for update to cache + * + * @param array $availableVersions + * @return bool|string + */ + private function savePackagesForUpdateToCache($availableVersions) + { + $syncInfo = []; + $syncInfo['lastSyncDate'] = str_replace('-', '/', $this->dateTime->formatDate(true)); + $syncInfo['packages'] = $availableVersions; + $data = json_encode($syncInfo, JSON_UNESCAPED_SLASHES); + try { + $this->directory->writeFile($this->pathToCacheFile, $data); + } catch (\Magento\Framework\Exception\FileSystemException $e) { + return false; + } + return $data; + } + + /** + * Load composer packages available for update from cache + * + * @return bool|string + */ + private function loadPackagesForUpdateFromCache() + { + if ($this->directory->isExist($this->pathToCacheFile) && $this->directory->isReadable($this->pathToCacheFile)) { + try { + $data = $this->directory->readFile($this->pathToCacheFile); + return json_decode($data, true); + } catch (\Magento\Framework\Exception\FileSystemException $e) { + } + } + return false; + } +}