diff --git a/.ci/runtests.sh b/.ci/runtests.sh index 0873d89ff4b..49ff49bd740 100755 --- a/.ci/runtests.sh +++ b/.ci/runtests.sh @@ -1,37 +1,22 @@ #!/bin/bash -SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) +set -evuo pipefail -if [ -z "${DISPLAY:-}" ]; then - echo Start Xvfb; - export DISPLAY=:99.0; - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x24; -fi - -if ! $(nc -z localhost 4444); then - $SELF_PATH/../vendor/bin/selenium-server-standalone -role hub -log $SELF_PATH/../selenium-server.log -enablePassThrough false & - until $(nc -z localhost 4444); do - echo Waiting for selenium hub to start...; - sleep 1; - done -fi +SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && /bin/pwd -P) +ROOT=$(realpath $SELF_PATH/..) +HOST=localhost -export PATH="$SELF_PATH/../vendor/bin:$PATH" -if ! $(nc -z localhost 8910); then - java -Dwebdriver.chrome.driver="$SELF_PATH/../vendor/bin/chromedriver" -jar $SELF_PATH/../vendor/se/selenium-server-standalone/bin/selenium-server-standalone.jar -role node -port 8910 -log $SELF_PATH/..selenium-node.log & - until $(nc -z localhost 8910); do - echo Waiting for selenium node to start...; - sleep 1; - done -fi +$SELF_PATH/start-selenium.sh -if ! $(nc -z localhost 8000); then - pushd $SELF_PATH/.. - php laravel serve & +if ! $(nc -z $HOST 8000); then + pushd $ROOT + php artisan serve --host=$HOST & popd - until $(nc -z localhost 8000); do + until $(nc -z $HOST 8000); do echo Waiting for laravel serve to start...; sleep 1; done fi -$SELF_PATH/../vendor/bin/steward run laravel chrome -vvv +pushd $ROOT +$ROOT/vendor/bin/steward run laravel chrome -vvv +popd diff --git a/.ci/start-selenium.sh b/.ci/start-selenium.sh index 930a8c65ccb..39683efd52e 100755 --- a/.ci/start-selenium.sh +++ b/.ci/start-selenium.sh @@ -1,13 +1,27 @@ #!/bin/bash + +realpath () +{ + f=$@; + if [ -d "$f" ]; then + base=""; + dir="$f"; + else + base="/$(basename "$f")"; + dir=$(dirname "$f"); + fi; + dir=$(cd "$dir" && /bin/pwd -P); + echo "$dir$base" +} set -evuo pipefail -SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) -ROOT=$SELF_PATH/.. +SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && /bin/pwd -P) +ROOT=$(realpath $SELF_PATH/..) if [ -z "${DISPLAY:-}" ]; then echo Start Xvfb; export DISPLAY=:99.0; - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x24; + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x24 || true; fi if ! $(nc -z localhost 4444); then diff --git a/.env.travis b/.env.travis index f9db091e75f..c1115e94941 100644 --- a/.env.travis +++ b/.env.travis @@ -12,4 +12,4 @@ CACHE_DRIVER=array SESSION_DRIVER=file QUEUE_DRIVER=sync -2FA_ENABLED=false +2FA_ENABLED=true diff --git a/composer.json b/composer.json index 650e4feac75..5646aefdcbd 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "require-dev": { "enm1989/chromedriver": "^2.35", "filp/whoops": "~2.0", + "khanamiryan/qrcode-detector-decoder": "^1.0", "lmc/steward": "dev-update-dependencies", "matthiasnoback/live-code-coverage": "^0.1.0", "mockery/mockery": "0.9.*", diff --git a/composer.lock b/composer.lock index f59cc1bca4f..ab63dd1da5d 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" ], - "content-hash": "bb59cf258b0e181fe56399176e1cd1ef", + "content-hash": "ea7855ab97118d5abdc68e6ef33d7af8", "packages": [ { "name": "aws/aws-sdk-php", @@ -5548,6 +5548,56 @@ ], "time": "2015-05-11T14:41:42+00:00" }, + { + "name": "khanamiryan/qrcode-detector-decoder", + "version": "1", + "source": { + "type": "git", + "url": "https://github.com/khanamiryan/php-qrcode-detector-decoder.git", + "reference": "96d5f80680b04803c4f1b69d6e01735e876b80c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/khanamiryan/php-qrcode-detector-decoder/zipball/96d5f80680b04803c4f1b69d6e01735e876b80c7", + "reference": "96d5f80680b04803c4f1b69d6e01735e876b80c7", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ], + "files": [ + "lib/common/customFunctions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ashot Khanamiryan", + "email": "a.khanamiryan@gmail.com", + "homepage": "https://github.com/khanamiryan", + "role": "Developer" + } + ], + "description": "QR code decoder / reader", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder", + "keywords": [ + "barcode", + "qr", + "zxing" + ], + "time": "2017-01-13T09:11:46+00:00" + }, { "name": "lmc/steward", "version": "dev-update-dependencies", diff --git a/resources/views/settings/security/2fa-enable.blade.php b/resources/views/settings/security/2fa-enable.blade.php index 22a7b05e8b8..2561b41cbc2 100644 --- a/resources/views/settings/security/2fa-enable.blade.php +++ b/resources/views/settings/security/2fa-enable.blade.php @@ -43,9 +43,9 @@
{{ trans('settings.2fa_enable_otp') }}

- Image of QR barcode + Image of QR barcode
- {{ trans('settings.2fa_enable_otp_help') }} {{ $secret }} + {{ trans('settings.2fa_enable_otp_help') }} {{ $secret }}

diff --git a/tests/BrowserSelenium/BaseTestCase.php b/tests/BrowserSelenium/BaseTestCase.php index e61d539cd56..d25fe933185 100644 --- a/tests/BrowserSelenium/BaseTestCase.php +++ b/tests/BrowserSelenium/BaseTestCase.php @@ -18,6 +18,10 @@ abstract class BaseTestCase extends AbstractTestCase /** @var string */ public static $baseUrl; + protected function getUrl() + { + } + public function setUp() { parent::setUp(); @@ -25,7 +29,7 @@ public function setUp() // Set base url according to environment switch (ConfigProvider::getInstance()->env) { case 'dev': - self::$baseUrl = 'http://monica.test/'; // env('APP_URL'); + self::$baseUrl = config('app.url'); break; case 'travis': self::$baseUrl = 'http://localhost:8000/'; @@ -51,27 +55,47 @@ public function setUp() * Init the WebDriver. * (init should be run with "before" phpunit annotation, but it doesn't work !). */ - public function init() + public function init($url = null) { - $this->wd->get(self::$baseUrl); + $uri = self::$baseUrl; + if (! isset($url) || $url == null) { + $url = ''; + } + if (ends_with($uri, '/')) { + $uri = substr($uri, 0, strlen($uri) - 1); + } + + if (! starts_with($url, '/')) { + $url = '/'.$url; + } + + $this->wd->get($uri.$url); } /** * Init WebDriver and pass the login form. */ - public function initAndLogin() + public function initAndLogin($url = null, $login = 'admin@admin.com', $password = 'admin') { - $this->init(); + if (! isset($url) || $url == null) { + $url = $this->getUrl(); + if ($url == null) { + $url = '/dashboard'; + } + } + $this->init($url); - if ($this->getCurrentPath() == '/') { - //$url = $this->wd->getCurrentURL(); - $this->findById('email')->sendKeys('admin@admin.com'); - $this->findById('password')->sendKeys('admin'); - $this->findByTag('button')->submit(); + switch ($this->getCurrentPath()) { + case '/': + case '/login': + $this->findById('email')->sendKeys($login); + $this->findById('password')->sendKeys($password); + $this->findByTag('button')->submit(); - $this->wd->wait()->until( - WebDriverExpectedCondition::urlContains('/dashboard') - ); + $this->wd->wait()->until( + WebDriverExpectedCondition::urlContains($url) + ); + break; } } @@ -84,4 +108,34 @@ public function getCurrentPath() parse_url($this->wd->getCurrentURL(), PHP_URL_PATH) ); } + + /** + * Get full uri for destination path. + */ + public function getDestUri($path) + { + $parse_url = parse_url($this->wd->getCurrentURL()); + $scheme = isset($parse_url['scheme']) ? $parse_url['scheme'].'://' : ''; + $host = isset($parse_url['host']) ? $parse_url['host'] : ''; + $port = isset($parse_url['port']) ? ':'.$parse_url['port'] : ''; + + if (starts_with($path, '/')) { + $path = substr($path, 1); + } + + return $scheme.$host.$port.'/'.$path; + } + + /** + * Get url for path, find correponding link, and click it. + */ + public function clickDestUri($path) + { + $uri = $this->getDestUri($path); + $link = $this->findByXpath("//a[@href='$uri']"); + $link->click(); + $this->wd->wait()->until( + WebDriverExpectedCondition::urlContains($path) + ); + } } diff --git a/tests/BrowserSelenium/Settings/MultiFAControllerTest.php b/tests/BrowserSelenium/Settings/MultiFAControllerTest.php new file mode 100644 index 00000000000..e36eba85647 --- /dev/null +++ b/tests/BrowserSelenium/Settings/MultiFAControllerTest.php @@ -0,0 +1,121 @@ +initAndLogin(); + $this->assertEquals('/settings/security', $this->getCurrentPath()); + } + + /** + * Test if the user has 2fa Enable Link in Security Page. + * @group multifa + */ + public function testHasSettings2faEnableLink() + { + // Ensure user admin@admin.com has disabled 2FA + //$user = User::where('email', 'admin@admin.com')->first(); + //$user->google2fa_secret = null; + //$user->save(); + + $this->openPage(); + $enableuri = $this->getDestUri('/settings/security/2fa-enable'); + + $enablelinks = $this->findMultipleByXpath("//a[@href='$enableuri']"); + $this->assertTrue(count($enablelinks) > 0, 'link /settings/security/2fa-enable not found'); + $this->assertEquals(1, count($enablelinks), 'too many /settings/security/2fa-enable links'); + + $enablelink = $enablelinks[0]; + $this->assertEquals('btn btn-primary', $enablelink->getAttribute('class')); + } + + /** + * Test the barcode generated in 2fa Enable Page. + * @group multifa + */ + public function testHas2faEnableBarCode() + { + // Ensure user admin@admin.com has disabled 2FA + //$user = User::where('email', 'admin@admin.com')->first(); + //$user->google2fa_secret = null; + //$user->save(); + + $this->openPage(); + + $this->clickDestUri('/settings/security/2fa-enable'); + + $barcodes = $this->findMultipleById('barcode'); + $this->assertGreaterThan(0, count($barcodes), 'barcode not present'); + $this->assertCount(1, $barcodes, 'too many barcodes'); + + $secretkeys = $this->findMultipleById('secretkey'); + $this->assertGreaterThan(0, count($secretkeys), 'secretkey not present'); + $this->assertCount(1, $secretkeys, 'too many secretkeys'); + } + + /** + * Test the barcode generated in 2fa Enable Page. + * @group multifa + * @group multifabarcode + */ + public function testBarCodeContent() + { + // Ensure user admin@admin.com has disabled 2FA + //$user = User::where('email', 'admin@admin.com')->first(); + //$user->google2fa_secret = null; + //$user->save(); + + $this->openPage(); + $this->clickDestUri('/settings/security/2fa-enable'); + + $barcode = $this->findById('barcode'); + $imgsrc = $barcode->getAttribute('src'); + + $key = $this->unparseBarcode($imgsrc); + $this->assertEquals(32, strlen($key)); + + $secretkey = $this->findById('secretkey'); + $this->log('secret key: %s', $secretkey->getText()); + + $this->assertEquals($secretkey->getText(), $key); + } + + private function unparseBarcode($imgsrc) + { + $this->assertStringStartsWith('data:image/png', $imgsrc); + + $imgcode = str_replace('data:image/png;base64,', '', $imgsrc); + $this->log('img code: %s', $imgcode); + + $qrcode = new QrReader(base64_decode($imgcode), QrReader::SOURCE_TYPE_BLOB); + $text = $qrcode->text(); + $this->log('img content: %s', $text); + $this->assertStringStartsWith('otpauth://totp/', $text); + + // unparse $text + // See PragmaRX\Google2FA\Support\QRCode getQRCodeUrl + // example + //otpauth://totp/monicalocal.test:admin%40admin.com?secret=H25L7JLI7I57KYE7U53BIIOUELWXMRE6&issuer=monicalocal.test + + $ret = preg_match('@^otpauth://totp/([^:]+):([^?]+)\?secret=([^&]+)&issuer=(.+)@i', $text, $matches); + $this->assertEquals(1, $ret, 'otp content does not match format'); + $this->assertCount(5, $matches); + + return $matches[3]; + } +} diff --git a/tests/BrowserSelenium/SimpleTest.php b/tests/BrowserSelenium/SimpleTest.php index c7a6fb28f21..fe116b31117 100644 --- a/tests/BrowserSelenium/SimpleTest.php +++ b/tests/BrowserSelenium/SimpleTest.php @@ -4,6 +4,11 @@ class SimpleTest extends BaseTestCase { + protected function getUrl() + { + return '/'; + } + /** * A basic browser test example. */