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') }}
-
+
- {{ 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.
*/