diff --git a/docs/daux/installation/010_Anforderungen/010_Systemanforderungen.md b/docs/daux/installation/010_Anforderungen/010_Systemanforderungen.md index 12cb1c7..1b01288 100644 --- a/docs/daux/installation/010_Anforderungen/010_Systemanforderungen.md +++ b/docs/daux/installation/010_Anforderungen/010_Systemanforderungen.md @@ -11,7 +11,7 @@ title: Systemanforderungen * 6.0.2 und höher * 6.1.x * Installation via [Composer](https://getcomposer.org) -* Themes +* Themes (weitere Themes nach Ergänzung der Templates möglich) * flow * wave diff --git a/src/Application/Model/d3backupcode.php b/src/Application/Model/d3backupcode.php index 7c68a6b..38a318b 100644 --- a/src/Application/Model/d3backupcode.php +++ b/src/Application/Model/d3backupcode.php @@ -32,8 +32,7 @@ class d3backupcode extends BaseModel */ public function generateCode($sUserId) { - $sCode = d3RandomGenerator::getRandomTotpBackupCode(); - + $sCode = $this->getRandomTotpBackupCode(); $this->assign( [ 'oxuserid' => $sUserId, @@ -44,6 +43,11 @@ class d3backupcode extends BaseModel return $sCode; } + public function getRandomTotpBackupCode() + { + return d3RandomGenerator::getRandomTotpBackupCode(); + } + /** * @param $code * @return false|string diff --git a/src/Application/Model/d3backupcodelist.php b/src/Application/Model/d3backupcodelist.php index b0b9d07..6d9739b 100644 --- a/src/Application/Model/d3backupcodelist.php +++ b/src/Application/Model/d3backupcodelist.php @@ -17,6 +17,8 @@ namespace D3\Totp\Application\Model; use D3\Totp\Application\Controller\Admin\d3user_totp; use Exception; +use OxidEsales\Eshop\Core\Config; +use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface; use OxidEsales\Eshop\Core\DatabaseProvider; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Model\ListModel; @@ -44,16 +46,32 @@ class d3backupcodelist extends ListModel $this->deleteAllFromUser($sUserId); for ($i = 1; $i <= 10; $i++) { - $oBackupCode = oxNew(d3backupcode::class); + $oBackupCode = $this->getD3BackupCodeObject(); $this->_backupCodes[] = $oBackupCode->generateCode($sUserId); $this->offsetSet(md5(rand()), $oBackupCode); } /** @var d3user_totp $oActView */ - $oActView = Registry::getConfig()->getActiveView(); + $oActView = $this->d3GetConfig()->getActiveView(); $oActView->setBackupCodes($this->_backupCodes); } + /** + * @return d3backupcode + */ + public function getD3BackupCodeObject() + { + return oxNew(d3backupcode::class); + } + + /** + * @return Config + */ + public function d3GetConfig() + { + return Registry::getConfig(); + } + /** * @throws Exception */ @@ -83,7 +101,7 @@ class d3backupcodelist extends ListModel */ public function verify($totp) { - $oDb = DatabaseProvider::getDb(); + $oDb = $this->d3GetDb(); $query = "SELECT oxid FROM ".$this->getBaseObject()->getViewName(). " WHERE ".$oDb->quoteIdentifier('backupcode')." = ".$oDb->quote($this->getBaseObject()->d3EncodeBC($totp))." AND ". @@ -91,18 +109,29 @@ class d3backupcodelist extends ListModel $sVerify = $oDb->getOne($query); - $this->getBaseObject()->delete($sVerify); + if ($sVerify) { + $this->getBaseObject()->delete($sVerify); + } return (bool) $sVerify; } + /** + * @return DatabaseInterface + * @throws DatabaseConnectionException + */ + public function d3GetDb() + { + return DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC); + } + /** * @param $sUserId * @throws DatabaseConnectionException */ public function deleteAllFromUser($sUserId) { - $oDb = DatabaseProvider::getDb(); + $oDb = $this->d3GetDb(); $query = "SELECT OXID FROM ".$oDb->quoteIdentifier($this->getBaseObject()->getCoreTableName()). " WHERE ".$oDb->quoteIdentifier('oxuserid')." = ".$oDb->quote($sUserId); @@ -122,7 +151,7 @@ class d3backupcodelist extends ListModel */ public function getAvailableCodeCount($sUserId) { - $oDb = DatabaseProvider::getDb(); + $oDb = $this->d3GetDb(); $query = "SELECT count(*) FROM ".$oDb->quoteIdentifier($this->getBaseObject()->getViewName()). " WHERE ".$oDb->quoteIdentifier('oxuserid')." = ".$oDb->quote($sUserId); diff --git a/src/tests/unit/Application/Model/d3backupcodeTest.php b/src/tests/unit/Application/Model/d3backupcodeTest.php new file mode 100644 index 0000000..6bf86f5 --- /dev/null +++ b/src/tests/unit/Application/Model/d3backupcodeTest.php @@ -0,0 +1,160 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\tests\unit\Application\Model; + +use D3\Totp\Application\Model\d3backupcode; +use D3\Totp\Application\Model\d3totp; +use D3\Totp\tests\unit\d3TotpUnitTestCase; +use OxidEsales\Eshop\Application\Model\User; +use OxidEsales\Eshop\Core\Config; +use OxidEsales\Eshop\Core\Registry; +use PHPUnit_Framework_MockObject_MockObject; +use ReflectionException; + +class d3backupcodeTest extends d3TotpUnitTestCase +{ + /** @var d3backupcode */ + protected $_oModel; + + /** + * setup basic requirements + */ + public function setUp() + { + parent::setUp(); + + $this->_oModel = oxNew(d3backupcode::class); + } + + public function tearDown() + { + parent::tearDown(); + + unset($this->_oModel); + } + + /** + * @test + * @throws ReflectionException + */ + public function generateCodePass() + { + $sTestUserId = 'testUserId'; + $sBackupCode = '123456'; + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcode::class, array( + 'getRandomTotpBackupCode', + 'd3EncodeBC', + )); + $oModelMock->method('getRandomTotpBackupCode')->willReturn($sBackupCode); + $oModelMock->method('d3EncodeBC')->will( + $this->returnCallback(function ($arg) { + return $arg; + }) + ); + + $this->_oModel = $oModelMock; + + $this->callMethod($this->_oModel, 'generateCode', array($sTestUserId)); + + $this->assertSame($sTestUserId, $this->_oModel->getFieldData('oxuserid')); + $this->assertSame($sBackupCode, $this->_oModel->getFieldData('backupcode')); + } + + /** + * @test + * @throws ReflectionException + */ + public function getRandomTotpBackupCodePass() + { + $this->assertRegExp( + '@[0-9]{6}@', + $this->callMethod($this->_oModel, 'getRandomTotpBackupCode') + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3EncodeBCPass() + { + /** @var User|PHPUnit_Framework_MockObject_MockObject $oUserMock */ + $oUserMock = $this->getMock(User::class, array(), array(), '', false); + $oUserMock->assign( + array( + 'oxpasssalt' => 'abcdefghijk' + ) + ); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcode::class, array( + 'd3GetUser', + )); + $oModelMock->method('d3GetUser')->willReturn($oUserMock); + + $this->_oModel = $oModelMock; + + $this->assertSame( + 'e10adc3949ba59abbe56e057f20f883e', + $this->callMethod($this->_oModel, 'd3EncodeBC', array('123456')) + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3GetUserReturnCachedUser() + { + /** @var User|PHPUnit_Framework_MockObject_MockObject $oUserMock */ + $oUserMock = $this->getMock(User::class, array(), array(), '', false); + $oUserMock->assign( + array( + 'oxid' => 'foobar' + ) + ); + + $this->_oModel->setUser($oUserMock); + + $this->assertSame( + $oUserMock, + $this->callMethod($this->_oModel, 'd3GetUser') + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3GetUserReturnCurrentUser() + { + Registry::getSession()->setVariable(d3totp::TOTP_SESSION_CURRENTUSER, 'foobar'); + + $oUser = $this->callMethod($this->_oModel, 'd3GetUser'); + + $this->assertInstanceOf( + User::class, + $oUser + ); + $this->assertNull( + $oUser->getId() + ); + } +} \ No newline at end of file diff --git a/src/tests/unit/Application/Model/d3backupcodelistTest.php b/src/tests/unit/Application/Model/d3backupcodelistTest.php new file mode 100644 index 0000000..fc4642f --- /dev/null +++ b/src/tests/unit/Application/Model/d3backupcodelistTest.php @@ -0,0 +1,378 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\tests\unit\Application\Model; + +use D3\Totp\Application\Model\d3backupcode; +use D3\Totp\Application\Model\d3backupcodelist; +use D3\Totp\Application\Model\d3totp; +use D3\Totp\tests\unit\d3TotpUnitTestCase; +use OxidEsales\Eshop\Application\Controller\FrontendController; +use OxidEsales\Eshop\Application\Model\User; +use OxidEsales\Eshop\Core\Config; +use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface; +use OxidEsales\Eshop\Core\Database\Adapter\Doctrine\Database; +use OxidEsales\Eshop\Core\DatabaseProvider; +use OxidEsales\Eshop\Core\Registry; +use PHPUnit_Framework_MockObject_MockObject; +use ReflectionException; + +class d3backupcodelistTest extends d3TotpUnitTestCase +{ + /** @var d3backupcodelist */ + protected $_oModel; + + /** + * setup basic requirements + */ + public function setUp() + { + parent::setUp(); + + $this->_oModel = oxNew(d3backupcodelist::class); + } + + public function tearDown() + { + parent::tearDown(); + + unset($this->_oModel); + } + + /** + * @test + * @throws ReflectionException + */ + public function generateBackupCodes() + { + /** @var FrontendController|PHPUnit_Framework_MockObject_MockObject $oConfigMock */ + $oViewMock = $this->getMock(FrontendController::class, array( + 'setBackupCodes' + )); + $oViewMock->expects($this->once())->method('setBackupCodes')->willReturn(true); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oConfigMock */ + $oConfigMock = $this->getMock(d3backupcode::class, array( + 'getActiveView' + )); + $oConfigMock->method('getActiveView')->willReturn($oViewMock); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'generateCode' + )); + $oBackupCodeMock->expects($this->exactly(10))->method('generateCode'); + $oBackupCodeMock->method('getD3BackupCodeObject')->willReturn($oBackupCodeMock); + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'deleteAllFromUser', + 'getD3BackupCodeObject', + 'd3GetConfig' + )); + $oModelMock->expects($this->once())->method('deleteAllFromUser')->willReturn(true); + $oModelMock->method('getD3BackupCodeObject')->willReturn($oBackupCodeMock); + $oModelMock->method('d3GetConfig')->willReturn($oConfigMock); + + $this->_oModel = $oModelMock; + + $this->callMethod($this->_oModel, 'generateBackupCodes', array('123456')); + } + + /** + * @test + * @throws ReflectionException + */ + public function getD3BackupCodeObjectReturnsRightObject() + { + $this->assertInstanceOf( + d3backupcode::class, + $this->callMethod($this->_oModel, 'getD3BackupCodeObject') + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3GetConfigReturnsRightObject() + { + $this->assertInstanceOf( + Config::class, + $this->callMethod($this->_oModel, 'd3GetConfig') + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function savePass() + { + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'save' + )); + $oBackupCodeMock->expects($this->once())->method('save')->willReturn(true); + + $aBackupCodeArray = [ + $oBackupCodeMock + ]; + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'getArray' + )); + $oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray); + + $this->_oModel = $oModelMock; + + $this->callMethod($this->_oModel, 'save'); + } + + /** + * @test + * @throws ReflectionException + */ + public function getBaseObjectReturnsRightObject() + { + $oBaseObject = $this->callMethod($this->_oModel, 'getBaseObject'); + + $this->assertInternalType('object', $oBaseObject); + $this->assertInstanceOf(d3backupcode::class, $oBaseObject); + } + + /** + * @test + * @throws ReflectionException + */ + public function verifyFoundTotp() + { + /** @var User|PHPUnit_Framework_MockObject_MockObject $oUserMock */ + $oUserMock = $this->getMock(User::class, array( + 'getId' + )); + $oUserMock->method('getId')->willReturn('foobar'); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'delete' + )); + $oBackupCodeMock->expects($this->once())->method('delete')->willReturn(true); + + /** @var Database|PHPUnit_Framework_MockObject_MockObject $oDbMock */ + $oDbMock = $this->getMock(Database::class, array( + 'getOne', + 'quoteIdentifier', + 'quote', + ), array(), '', false); + $oDbMock->expects($this->once())->method('getOne')->willReturn('foobar'); + $oDbMock->method('quoteIdentifier')->willReturn(true); + $oDbMock->method('quote')->willReturn(true); + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'd3GetDb', + 'getBaseObject', + 'd3GetUser' + )); + $oModelMock->method('d3GetDb')->willReturn($oDbMock); + $oModelMock->method('getBaseObject')->willReturn($oBackupCodeMock); + $oModelMock->method('d3GetUser')->willReturn($oUserMock); + + $this->_oModel = $oModelMock; + + $this->assertTrue( + $this->callMethod($this->_oModel, 'verify', array('123456')) + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function verifyNotFoundTotp() + { + /** @var User|PHPUnit_Framework_MockObject_MockObject $oUserMock */ + $oUserMock = $this->getMock(User::class, array( + 'getId' + )); + $oUserMock->method('getId')->willReturn('foobar'); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'delete' + )); + $oBackupCodeMock->expects($this->never())->method('delete')->willReturn(true); + + /** @var Database|PHPUnit_Framework_MockObject_MockObject $oDbMock */ + $oDbMock = $this->getMock(Database::class, array( + 'getOne', + 'quoteIdentifier', + 'quote', + ), array(), '', false); + $oDbMock->expects($this->once())->method('getOne')->willReturn(null); + $oDbMock->method('quoteIdentifier')->willReturn(true); + $oDbMock->method('quote')->willReturn(true); + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'd3GetDb', + 'getBaseObject', + 'd3GetUser' + )); + $oModelMock->method('d3GetDb')->willReturn($oDbMock); + $oModelMock->method('getBaseObject')->willReturn($oBackupCodeMock); + $oModelMock->method('d3GetUser')->willReturn($oUserMock); + + $this->_oModel = $oModelMock; + + $this->assertFalse( + $this->callMethod($this->_oModel, 'verify', array('123456')) + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3GetDbReturnsRightInstance() + { + $this->assertInstanceOf( + Database::class, + $this->callMethod($this->_oModel, 'd3GetDb') + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function deleteAllFromUserCodesFound() + { + /** @var Database|PHPUnit_Framework_MockObject_MockObject $oDbMock */ + $oDbMock = $this->getMock(Database::class, array( + 'quoteIdentifier', + 'quote', + ), array(), '', false); + $oDbMock->method('quoteIdentifier')->willReturn(true); + $oDbMock->method('quote')->willReturn(true); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'delete' + )); + $oBackupCodeMock->expects($this->once())->method('delete')->willReturn(true); + + $aBackupCodeArray = [ + $oBackupCodeMock + ]; + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'getArray', + 'selectString', + 'd3GetDb' + )); + $oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray); + $oModelMock->expects($this->once())->method('selectString')->willReturn(true); + $oModelMock->method('d3GetDb')->willReturn($oDbMock); + + $this->_oModel = $oModelMock; + + $this->callMethod($this->_oModel, 'deleteAllFromUser', ['foobar']); + } + + /** + * @test + * @throws ReflectionException + */ + public function deleteAllFromUserNoCodesFound() + { + /** @var Database|PHPUnit_Framework_MockObject_MockObject $oDbMock */ + $oDbMock = $this->getMock(Database::class, array( + 'quoteIdentifier', + 'quote', + ), array(), '', false); + $oDbMock->method('quoteIdentifier')->willReturn(true); + $oDbMock->method('quote')->willReturn(true); + + /** @var d3backupcode|PHPUnit_Framework_MockObject_MockObject $oBackupCodeMock */ + $oBackupCodeMock = $this->getMock(d3backupcode::class, array( + 'delete' + )); + $oBackupCodeMock->expects($this->never())->method('delete')->willReturn(true); + + $aBackupCodeArray = []; + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'getArray', + 'selectString', + 'd3GetDb' + )); + $oModelMock->expects($this->once())->method('getArray')->willReturn($aBackupCodeArray); + $oModelMock->expects($this->once())->method('selectString')->willReturn(true); + $oModelMock->method('d3GetDb')->willReturn($oDbMock); + + $this->_oModel = $oModelMock; + + $this->callMethod($this->_oModel, 'deleteAllFromUser', ['foobar']); + } + + /** + * @test + * @throws ReflectionException + */ + public function getAvailableCodeCountPass() + { + /** @var Database|PHPUnit_Framework_MockObject_MockObject $oDbMock */ + $oDbMock = $this->getMock(Database::class, array( + 'getOne', + 'quoteIdentifier', + 'quote', + ), array(), '', false); + $oDbMock->expects($this->once())->method('getOne')->willReturn('25'); + $oDbMock->method('quoteIdentifier')->willReturn(true); + $oDbMock->method('quote')->willReturn(true); + + /** @var d3backupcodelist|PHPUnit_Framework_MockObject_MockObject $oModelMock */ + $oModelMock = $this->getMock(d3backupcodelist::class, array( + 'd3GetDb', + )); + $oModelMock->method('d3GetDb')->willReturn($oDbMock); + + $this->_oModel = $oModelMock; + + $this->assertSame( + 25, + $this->callMethod($this->_oModel, 'getAvailableCodeCount', ['foobar']) + ); + } + + /** + * @test + * @throws ReflectionException + */ + public function d3GetUserReturnsRightInstance() + { + $this->assertInstanceOf( + User::class, + $this->callMethod($this->_oModel, 'd3GetUser') + ); + } +} \ No newline at end of file