Merge pull request #9823 from MrPetovan/task/9677-2fa-remember-device

Add "Remember this device" feature to two factor authentication
This commit is contained in:
Michael Vogel 2021-01-27 22:32:08 +01:00 committed by GitHub
commit 199f72ee3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 988 additions and 248 deletions

View file

@ -35,22 +35,24 @@ class StaticCookie extends Cookie
/**
* Send a cookie - protected, internal function for test-mocking possibility
* @see Cookie::setCookie()
*
* @link https://php.net/manual/en/function.setcookie.php
*
* @param string $name
* @param string $value [optional]
* @param int $expire [optional]
* @param bool $secure [optional]
* @return bool
*
* @noinspection PhpMissingParentCallCommonInspection
*
* @link https://php.net/manual/en/function.setcookie.php
*
* @see Cookie::setCookie()
*/
protected function setCookie(string $name, string $value = null, int $expire = null, bool $secure = null)
protected function setCookie(string $value = null, int $expire = null, bool $secure = null): bool
{
self::$_COOKIE[$name] = $value;
self::$_COOKIE[self::NAME] = $value;
self::$_EXPIRE = $expire;
return true;
}
public static function clearStatic()

View file

@ -128,30 +128,20 @@ class CookieTest extends MockedTest
$cookie = new Cookie($this->config, $this->baseUrl, [], $cookieData);
self::assertInstanceOf(Cookie::class, $cookie);
$assertData = $cookie->getData();
if (!$hasValues) {
self::assertEmpty($assertData);
if (isset($uid)) {
self::assertEquals($uid, $cookie->get('uid'));
} else {
self::assertNotEmpty($assertData);
if (isset($uid)) {
self::assertObjectHasAttribute('uid', $assertData);
self::assertEquals($uid, $assertData->uid);
} else {
self::assertObjectNotHasAttribute('uid', $assertData);
}
if (isset($hash)) {
self::assertObjectHasAttribute('hash', $assertData);
self::assertEquals($hash, $assertData->hash);
} else {
self::assertObjectNotHasAttribute('hash', $assertData);
}
if (isset($ip)) {
self::assertObjectHasAttribute('ip', $assertData);
self::assertEquals($ip, $assertData->ip);
} else {
self::assertObjectNotHasAttribute('ip', $assertData);
}
self::assertNull($cookie->get('uid'));
}
if (isset($hash)) {
self::assertEquals($hash, $cookie->get('hash'));
} else {
self::assertNull($cookie->get('hash'));
}
if (isset($ip)) {
self::assertEquals($ip, $cookie->get('ip'));
} else {
self::assertNull($cookie->get('ip'));
}
}
@ -196,7 +186,7 @@ class CookieTest extends MockedTest
$cookie = new Cookie($this->config, $this->baseUrl);
self::assertInstanceOf(Cookie::class, $cookie);
self::assertEquals($assertTrue, $cookie->check($assertHash, $password, $userPrivateKey));
self::assertEquals($assertTrue, $cookie->comparePrivateDataHash($assertHash, $password, $userPrivateKey));
}
public function dataSet()
@ -210,7 +200,6 @@ class CookieTest extends MockedTest
'assertHash' => 'b657a15cfe7ed1f7289c9aa51af14a9a26c966f4ddd74e495fba103d8e872a39',
'remoteIp' => '0.0.0.0',
'serverArray' => [],
'lifetime' => null,
],
'withServerArray' => [
'serverKey' => 23,
@ -220,32 +209,11 @@ class CookieTest extends MockedTest
'assertHash' => 'b657a15cfe7ed1f7289c9aa51af14a9a26c966f4ddd74e495fba103d8e872a39',
'remoteIp' => '1.2.3.4',
'serverArray' => ['REMOTE_ADDR' => '1.2.3.4',],
'lifetime' => null,
],
'withLifetime0' => [
'serverKey' => 23,
'uid' => 0,
'password' => '234',
'privateKey' => '124',
'assertHash' => 'b657a15cfe7ed1f7289c9aa51af14a9a26c966f4ddd74e495fba103d8e872a39',
'remoteIp' => '1.2.3.4',
'serverArray' => ['REMOTE_ADDR' => '1.2.3.4',],
'lifetime' => 0,
],
'withLifetime' => [
'serverKey' => 23,
'uid' => 0,
'password' => '234',
'privateKey' => '124',
'assertHash' => 'b657a15cfe7ed1f7289c9aa51af14a9a26c966f4ddd74e495fba103d8e872a39',
'remoteIp' => '1.2.3.4',
'serverArray' => ['REMOTE_ADDR' => '1.2.3.4',],
'lifetime' => 2 * 24 * 60 * 60,
],
];
}
public function assertCookie($uid, $hash, $remoteIp, $lifetime)
public function assertCookie($uid, $hash, $remoteIp)
{
self::assertArrayHasKey(Cookie::NAME, StaticCookie::$_COOKIE);
@ -258,11 +226,7 @@ class CookieTest extends MockedTest
self::assertObjectHasAttribute('ip', $data);
self::assertEquals($remoteIp, $data->ip);
if (isset($lifetime) && $lifetime !== 0) {
self::assertLessThanOrEqual(time() + $lifetime, StaticCookie::$_EXPIRE);
} else {
self::assertLessThanOrEqual(time() + Cookie::DEFAULT_EXPIRE * 24 * 60 * 60, StaticCookie::$_EXPIRE);
}
self::assertLessThanOrEqual(time() + Cookie::DEFAULT_EXPIRE * 24 * 60 * 60, StaticCookie::$_EXPIRE);
}
/**
@ -270,7 +234,7 @@ class CookieTest extends MockedTest
*
* @dataProvider dataSet
*/
public function testSet($serverKey, $uid, $password, $privateKey, $assertHash, $remoteIp, $serverArray, $lifetime)
public function testSet($serverKey, $uid, $password, $privateKey, $assertHash, $remoteIp, $serverArray)
{
$this->baseUrl->shouldReceive('getSSLPolicy')->andReturn(true)->once();
$this->config->shouldReceive('get')->with('system', 'site_prvkey')->andReturn($serverKey)->once();
@ -279,17 +243,20 @@ class CookieTest extends MockedTest
$cookie = new StaticCookie($this->config, $this->baseUrl, $serverArray);
self::assertInstanceOf(Cookie::class, $cookie);
$cookie->set($uid, $password, $privateKey, $lifetime);
$cookie->setMultiple([
'uid' => $uid,
'hash' => $assertHash,
]);
self::assertCookie($uid, $assertHash, $remoteIp, $lifetime);
self::assertCookie($uid, $assertHash, $remoteIp);
}
/**
* Test two different set() of the cookie class (first set is invalid)
* Test the set() method of the cookie class
*
* @dataProvider dataSet
*/
public function testDoubleSet($serverKey, $uid, $password, $privateKey, $assertHash, $remoteIp, $serverArray, $lifetime)
public function testDoubleSet($serverKey, $uid, $password, $privateKey, $assertHash, $remoteIp, $serverArray)
{
$this->baseUrl->shouldReceive('getSSLPolicy')->andReturn(true)->once();
$this->config->shouldReceive('get')->with('system', 'site_prvkey')->andReturn($serverKey)->once();
@ -298,12 +265,10 @@ class CookieTest extends MockedTest
$cookie = new StaticCookie($this->config, $this->baseUrl, $serverArray);
self::assertInstanceOf(Cookie::class, $cookie);
// Invalid set, should get overwritten
$cookie->set(-1, 'invalid', 'nothing', -234);
$cookie->set('uid', $uid);
$cookie->set('hash', $assertHash);
$cookie->set($uid, $password, $privateKey, $lifetime);
self::assertCookie($uid, $assertHash, $remoteIp, $lifetime);
self::assertCookie($uid, $assertHash, $remoteIp);
}
/**

View file

@ -0,0 +1,62 @@
<?php
namespace Friendica\Test\src\Security\TwoFactor\Factory;
use Friendica\Security\TwoFactor\Factory\TrustedBrowser;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Logger\VoidLogger;
use Friendica\Util\Strings;
class TrustedBrowserTest extends \PHPUnit_Framework_TestCase
{
public function testCreateFromTableRowSuccess()
{
$factory = new TrustedBrowser(new VoidLogger());
$row = [
'cookie_hash' => Strings::getRandomHex(),
'uid' => 42,
'user_agent' => 'PHPUnit',
'created' => DateTimeFormat::utcNow(),
'last_used' => null,
];
$trustedBrowser = $factory->createFromTableRow($row);
$this->assertEquals($row, $trustedBrowser->toArray());
}
public function testCreateFromTableRowMissingData()
{
$this->expectException(\TypeError::class);
$factory = new TrustedBrowser(new VoidLogger());
$row = [
'cookie_hash' => null,
'uid' => null,
'user_agent' => null,
'created' => null,
'last_used' => null,
];
$trustedBrowser = $factory->createFromTableRow($row);
$this->assertEquals($row, $trustedBrowser->toArray());
}
public function testCreateForUserWithUserAgent()
{
$factory = new TrustedBrowser(new VoidLogger());
$uid = 42;
$userAgent = 'PHPUnit';
$trustedBrowser = $factory->createForUserWithUserAgent($uid, $userAgent);
$this->assertNotEmpty($trustedBrowser->cookie_hash);
$this->assertEquals($uid, $trustedBrowser->uid);
$this->assertEquals($userAgent, $trustedBrowser->user_agent);
$this->assertNotEmpty($trustedBrowser->created);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Friendica\Test\src\Security\TwoFactor\Model;
use Friendica\Security\TwoFactor\Model\TrustedBrowser;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
class TrustedBrowserTest extends \PHPUnit_Framework_TestCase
{
public function test__construct()
{
$hash = Strings::getRandomHex();
$trustedBrowser = new TrustedBrowser(
$hash,
42,
'PHPUnit',
DateTimeFormat::utcNow()
);
$this->assertEquals($hash, $trustedBrowser->cookie_hash);
$this->assertEquals(42, $trustedBrowser->uid);
$this->assertEquals('PHPUnit', $trustedBrowser->user_agent);
$this->assertNotEmpty($trustedBrowser->created);
}
public function testRecordUse()
{
$hash = Strings::getRandomHex();
$past = DateTimeFormat::utc('now - 5 minutes');
$trustedBrowser = new TrustedBrowser(
$hash,
42,
'PHPUnit',
$past,
$past
);
$trustedBrowser->recordUse();
$this->assertEquals($past, $trustedBrowser->created);
$this->assertGreaterThan($past, $trustedBrowser->last_used);
}
}