Compare commits

..

1 Commits

Author SHA1 Message Date
Daniel Seifert 9c768d4af7
bump webauthn lib to 4.5 2023-02-05 22:06:56 +01:00
39 changed files with 372 additions and 910 deletions

View File

@ -4,14 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased](https://git.d3data.de/D3Public/webauthn/compare/1.0.0.0...rel_1.x)
## [Unreleased](https://git.d3data.de/D3Public/webauthn/compare/2.0.0.1...rel_2.x)
## [1.0.0.0](https://git.d3data.de/D3Public/webauthn/compare/0.1.0.0...1.0.0.0) - 2023-05-25
## [1.0.0.0](https://git.d3data.de/D3Public/webauthn/releases/tag/1.0.0.0) - 2019-08-19
### Added
- make installable in OXID 6.5.2
## [0.1.0.0](https://git.d3data.de/D3Public/webauthn/releases/tag/0.1.0.0) - 2023-02-18
### Added
- Key management in front and back end
- FIDO2 / passkey as password alternative for login in front- and backend - password as fallback
- compatible with our 2FA one-time password module (https://packagist.org/packages/d3/oxid-twofactor-onetimepassword from version 2.1.0.0)
- 2-factor authentication for logins in front- and backend in addition to username and password
- Activation and setup possible in the front and back end
- Authentication is shown for user accounts that have this enabled - otherwise the usual default login.
- Access can be set up in the Auth app by scannable QR code or copyable character string
- Validation of one-time passwords and generation of QR codes are only carried out within the shop - no communication to the outside necessary
- static backup codes also allow (limited) login without access to the generation tool

View File

@ -1,15 +1,13 @@
[![deutsche Version](https://logos.oxidmodule.com/de2_xs.svg)](README.md)
[![english version](https://logos.oxidmodule.com/en2_xs.svg)](README.en.md)
# Passwordless login for OXID eShop
# Passwordless login for OXID eShop
![Passwordless login for OXID eShop](src/logo.png)
With this module, the login in the OXID shop can be carried out with a hardware based login key (WebAuthn / FIDO2 based passkey) instead of a password.
With this module, the login in the OXID shop can be carried out with a hardware token instead of a password (WebAuthn / FIDO2 based).
This secures the login in the frontend and (if allowed for the user) also in the backend.
Login keys are from devices that contain cryptographic keys. These can be used for two-factor authentication. The login key device must support the standard "[WebAuthn](https://w3c.github.io/webauthn/#webauthn-authenticator)".
Security keys are devices that contain cryptographic keys. These can be used for two-factor authentication. The security key must support the standard "[WebAuthn](https://w3c.github.io/webauthn/#webauthn-authenticator)".
The key management is done in the admin area and in the user's "My Account".
@ -65,14 +63,12 @@ and its requirements.
The Flow and Wave themes are supported by default. Other themes may require customisation.
## Module installation / update
## Module installation
Open a command line interface and navigate to the shop root directory (parent of source and vendor). Execute the following commands. Adapt the paths to your environment.
Open a command line interface and navigate to the shop root directory (parent of source and vendor). Execute the following command. Adapt the paths to your environment.
```bash
composer require d3/oxid-twofactor-passwordless:^1.0
./vendor/bin/oe-eshop-db_migrate migrations:migrate d3webauthn
php composer require d3/oxid-twofactor-passwordless:^1.0
```
If a reference to an unsuitable package `symfony/process` is shown, this must be changed. To do this, please add the switch `-W` to the above command (`... require -W ...`).

View File

@ -1,15 +1,13 @@
[![deutsche Version](https://logos.oxidmodule.com/de2_xs.svg)](README.md)
[![english version](https://logos.oxidmodule.com/en2_xs.svg)](README.en.md)
# Passwortloses Anmelden für OXID eShop
# Passwortloses Anmelden für OXID eShop
![Passwortloses Anmelden für OXID eShop](src/logo.png)
Mit diesem Modul kann die Anmeldung im OXID-Shop mit einem hardwarebasierten Anmeldeschlüssel (WebAuthn / FIDO2 basierter passkey) anstelle eines Passworts durchgeführt werden.
Mit diesem Modul kann die Anmeldung im OXID-Shop mit einem Hardwaretoken anstelle eines Passworts durchgeführt werden (WebAuthn / FIDO2 basiert).
Hierbei wird die Anmeldung im Frontend und (sofern für den Benutzer erlaubt) auch im Backend gesichert.
Anmeldeschlüssel stammen von Geräten, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Das Anmeldeschlüsselgerät muss den Standard "[WebAuthn](https://w3c.github.io/webauthn/#webauthn-authenticator)" unterstützen.
Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard "[WebAuthn](https://w3c.github.io/webauthn/#webauthn-authenticator)" unterstützen.
Die Schlüsselverwaltung erfolgt im Adminbereich sowie im "Mein Konto" des Benutzers.
@ -65,14 +63,12 @@ und dessen Anforderungen.
Im Standard wird das Flow- und Wave-Theme unterstützt. Andere Themes können Anpassungen erfordern.
## Modulinstallation / -update
## Modulinstallation
Öffnen Sie eine Kommandozeile und navigieren Sie zum Stammverzeichnis des Shops (Elternverzeichnis von source und vendor). Führen Sie die folgenden Befehle aus. Passen Sie die Pfadangaben an Ihre Installationsumgebung an.
Öffnen Sie eine Kommandozeile und navigieren Sie zum Stammverzeichnis des Shops (Elternverzeichnis von source und vendor). Führen Sie den folgenden Befehl aus. Passen Sie die Pfadangaben an Ihre Installationsumgebung an.
```bash
composer require d3/oxid-twofactor-passwordless:^1.0
./vendor/bin/oe-eshop-db_migrate migrations:migrate d3webauthn
php composer require d3/oxid-twofactor-passwordless:^1.0
```
Wird ein Hinweis auf ein unpassendes Paket "symfony/process" gezeigt, muss dieses geändert werden. Fügen Sie dazu in den oben genannten Befehl bitte den Schalter `-W` ein (`... require -W ...`).

View File

@ -18,9 +18,7 @@
"token",
"yubikey",
"solokey",
"credential",
"login",
"passkey"
"credential"
],
"authors": [
{
@ -42,8 +40,8 @@
},
"require": {
"php": ">=7.4",
"oxid-esales/oxideshop-ce": "6.8 - 6.14",
"web-auth/webauthn-lib": "^3.3",
"oxid-esales/oxideshop-ce": "6.8 - 6.13",
"web-auth/webauthn-lib": "^4.3",
"nyholm/psr7": "^1.5.1",
"nyholm/psr7-server": "^1.0.2",
"ext-json": "*",
@ -57,8 +55,7 @@
},
"autoload": {
"psr-4": {
"D3\\Webauthn\\": "../../../source/modules/d3/oxwebauthn",
"D3\\Webauthn\\Migrations\\": "../../../source/modules/d3/oxwebauthn/migration/data"
"D3\\Webauthn\\": "../../../source/modules/d3/oxwebauthn"
}
},
"suggest": {

View File

@ -15,9 +15,7 @@ declare(strict_types=1);
namespace D3\Webauthn\Application\Controller\Admin;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
@ -85,7 +83,7 @@ class d3user_webauthn extends AdminDetailsController
try {
$this->setPageType('requestnew');
$this->setAuthnRegister();
} catch (AssertionFailedException|ContainerExceptionInterface|NotFoundExceptionInterface|DoctrineDriverException $e) {
} catch (Exception|ContainerExceptionInterface|NotFoundExceptionInterface|DoctrineDriverException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e->getMessage());
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getMessage(), ['UserId' => $this->getEditObjectId()]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
@ -109,15 +107,12 @@ class d3user_webauthn extends AdminDetailsController
}
$credential = Registry::getRequest()->getRequestEscapedParameter('credential');
Assert::that($credential)->minLength(1, 'Credential should not be empty.');
$keyname = Registry::getRequest()->getRequestEscapedParameter('keyname');
Assert::that($keyname)->minLength(1, 'Key name should not be empty.');
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($credential);
/** @var Webauthn $webauthn */
$webauthn = d3GetOxidDIC()->get(Webauthn::class);
$webauthn->saveAuthn($credential, $keyname);
if (strlen((string) $credential)) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($credential);
/** @var Webauthn $webauthn */
$webauthn = d3GetOxidDIC()->get(Webauthn::class);
$webauthn->saveAuthn($credential, Registry::getRequest()->getRequestEscapedParameter('keyname'));
}
} catch (WebauthnException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getDetailedErrorMessage(), ['UserId' => $this->getEditObjectId()]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
@ -141,14 +136,11 @@ class d3user_webauthn extends AdminDetailsController
/**
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws InvalidArgumentException
*/
public function setAuthnRegister(): void
{
/** @var Webauthn $authn */
$authn = d3GetOxidDIC()->get(Webauthn::class);
/** @var User $user */
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$user->load($this->getEditObjectId());
$publicKeyCredentialCreationOptions = $authn->getCreationOptions($user);
@ -173,7 +165,6 @@ class d3user_webauthn extends AdminDetailsController
*/
public function getCredentialList($userId): array
{
/** @var User $oUser */
$oUser = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$oUser->load($userId);

View File

@ -15,9 +15,6 @@ declare(strict_types=1);
namespace D3\Webauthn\Application\Controller\Admin;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Model\Exceptions\WebauthnGetException;
use D3\Webauthn\Application\Model\Webauthn;
@ -29,6 +26,7 @@ use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
use Doctrine\DBAL\Exception as DoctrineException;
use OxidEsales\Eshop\Application\Controller\Admin\AdminController;
use OxidEsales\Eshop\Application\Controller\FrontendController;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\Eshop\Core\Request;
use OxidEsales\Eshop\Core\Routing\ControllerClassNameResolver;
use OxidEsales\Eshop\Core\Session;
@ -62,11 +60,11 @@ class d3webauthnadminlogin extends AdminController
public function render(): string
{
if (d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->hasVariable(WebauthnConf::WEBAUTHN_ADMIN_SESSION_AUTH)
->hasVariable(WebauthnConf::WEBAUTHN_ADMIN_SESSION_AUTH)
) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=admin_start');
} elseif (!d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->hasVariable(WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER)
->hasVariable(WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER)
) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=login');
}
@ -109,18 +107,11 @@ class d3webauthnadminlogin extends AdminController
$this->addTplParam('isAdmin', isAdmin());
} catch (WebauthnException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->setVariable(WebauthnConf::GLOBAL_SWITCH, true);
->setVariable(WebauthnConf::GLOBAL_SWITCH, true);
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getDetailedErrorMessage(), ['UserId' => $userId]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=login');
} catch (AssertionFailedException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->setVariable(WebauthnConf::GLOBAL_SWITCH, true);
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getMessage(), ['UserId' => $userId]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=login');
}
}
@ -131,10 +122,10 @@ class d3webauthnadminlogin extends AdminController
{
try {
$login = $this->getWebAuthnLogin();
$profile = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('profile');
Assert::that($profile)->string();
return $login->adminLogin($profile);
} catch (WebauthnGetException|AssertionFailedException $e) {
return $login->adminLogin(
d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('profile')
);
} catch (WebauthnGetException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
return 'login';
}
@ -174,19 +165,16 @@ class d3webauthnadminlogin extends AdminController
/**
* @return WebauthnLogin
* @throws InvalidArgumentException
*/
protected function getWebAuthnLogin(): WebauthnLogin
{
/** @var Request $request */
$request = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class);
$credential = $request->getRequestEscapedParameter('credential');
$error = $request->getRequestEscapedParameter('error');
Assert::that($credential)->string('credential value expected to be string');
Assert::that($error)->string('error value expected to be string');
return oxNew(WebauthnLogin::class, $credential, $error);
return oxNew(
WebauthnLogin::class,
$request->getRequestEscapedParameter('credential'),
$request->getRequestEscapedParameter('error')
);
}
}

View File

@ -15,9 +15,7 @@ declare(strict_types=1);
namespace D3\Webauthn\Application\Controller;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Controller\Traits\accountTrait;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
@ -88,10 +86,6 @@ class d3_account_webauthn extends AccountController
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getDetailedErrorMessage(), ['UserId: ' => $this->getUser()->getId()]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
} catch (AssertionFailedException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getMessage(), ['UserId: ' => $this->getUser()->getId()]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
}
}
@ -109,7 +103,6 @@ class d3_account_webauthn extends AccountController
* @throws DoctrineException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws InvalidArgumentException
* @return void
*/
public function setAuthnRegister(): void
@ -144,10 +137,11 @@ class d3_account_webauthn extends AccountController
}
$credential = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('credential');
Assert::that($credential)->minLength(1, 'Credential should not be empty.');
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($credential);
$webauthn = d3GetOxidDIC()->get(Webauthn::class);
$webauthn->saveAuthn($credential, d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('keyname'));
if (strlen((string) $credential)) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($credential);
$webauthn = d3GetOxidDIC()->get(Webauthn::class);
$webauthn->saveAuthn($credential, d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('keyname'));
}
} catch (WebauthnException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error(
$e->getDetailedErrorMessage(),

View File

@ -15,7 +15,6 @@ declare(strict_types=1);
namespace D3\Webauthn\Application\Controller;
use Assert\AssertionFailedException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Model\Webauthn;
use D3\Webauthn\Application\Model\WebauthnConf;
@ -63,9 +62,9 @@ class d3webauthnlogin extends FrontendController
public function render(): string
{
if (d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->hasVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) ||
->hasVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) ||
!d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->hasVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER)
->hasVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER)
) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=start');
}
@ -73,7 +72,7 @@ class d3webauthnlogin extends FrontendController
$this->generateCredentialRequest();
$this->addTplParam('navFormParams', d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->getVariable(WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS));
->getVariable(WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS));
return $this->d3CallMockableFunction([FrontendController::class, 'render']);
}
@ -104,13 +103,6 @@ class d3webauthnlogin extends FrontendController
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
Registry::getUtilsView()->addErrorToDisplay($e);
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=start');
} catch (AssertionFailedException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
->setVariable(WebauthnConf::GLOBAL_SWITCH, true);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getMessage(), ['UserId' => $userId]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
Registry::getUtilsView()->addErrorToDisplay($e->getMessage());
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect('index.php?cl=start');
}
}

View File

@ -17,9 +17,8 @@ namespace D3\Webauthn\Application\Model\Credential;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Migrations\Version20230209212939;
use D3\Webauthn\Setup\Actions;
use DateTime;
use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
use Doctrine\DBAL\Exception as DoctrineException;
@ -76,7 +75,7 @@ class PublicKeyCredential extends BaseModel
Assert::that($encodedCID)
->maxLength(
Version20230209212939::FIELDLENGTH_CREDID,
Actions::FIELDLENGTH_CREDID,
'the credentialId (%3$d) does not fit into the database field (%2$d)'
);
@ -86,16 +85,11 @@ class PublicKeyCredential extends BaseModel
}
/**
* @return string
* @throws InvalidArgumentException
* @return null|string
*/
public function getCredentialId(): ?string
{
$encodedCID = $this->__get($this->_getFieldLongName('credentialid'))->rawValue;
Assert::that($encodedCID)->base64('Credential ID "%s" is not a valid base64 string.');
return base64_decode($encodedCID);
return base64_decode($this->__get($this->_getFieldLongName('credentialid'))->rawValue) ?: null;
}
/**
@ -126,7 +120,7 @@ class PublicKeyCredential extends BaseModel
Assert::that($encodedCredential)
->maxLength(
Version20230209212939::FIELDLENGTH_CREDENTIAL,
Actions::FIELDLENGTH_CREDENTIAL,
'the credential source (%3$d) does not fit into the database field (%2$d)',
);

View File

@ -15,9 +15,7 @@ declare(strict_types=1);
namespace D3\Webauthn\Application\Model;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
@ -26,6 +24,7 @@ use D3\Webauthn\Application\Model\Exceptions\WebauthnGetException;
use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn;
use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
use Doctrine\DBAL\Exception as DoctrineException;
use Exception;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
use OxidEsales\Eshop\Application\Model\User;
@ -77,7 +76,6 @@ class Webauthn
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws NotFoundExceptionInterface
* @throws InvalidArgumentException
*/
public function getCreationOptions(User $user): string
{
@ -96,7 +94,9 @@ class Webauthn
$json = $this->jsonEncode($publicKeyCredentialCreationOptions);
Assert::that($json)->isJsonString("can't encode request options");
if ($json === false) {
throw oxNew(Exception::class, "can't encode creation options");
}
return $json;
}
@ -134,7 +134,6 @@ class Webauthn
* @return string
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws InvalidArgumentException
*/
public function getRequestOptions(string $userId): string
{
@ -144,16 +143,11 @@ class Webauthn
d3GetOxidDIC()->set(UserEntity::class.'.args.user', $user);
/** @var UserEntity $userEntity */
$userEntity = d3GetOxidDIC()->get(UserEntity::class);
$existingCredentials = $this->getExistingCredentials($userEntity);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug(
'found user credentials: '.count($existingCredentials).' for ID '.$userId
);
// We generate the set of options.
$publicKeyCredentialRequestOptions = $this->getServer()->generatePublicKeyCredentialRequestOptions(
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value
$existingCredentials
$this->getExistingCredentials($userEntity)
);
d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
@ -161,11 +155,9 @@ class Webauthn
$json = $this->jsonEncode($publicKeyCredentialRequestOptions);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug(
'request options: '.$json
);
Assert::that($json)->isJsonString("can't encode request options");
if ($json === false) {
throw oxNew(Exception::class, "can't encode request options");
}
return $json;
}

View File

@ -17,7 +17,6 @@ namespace D3\Webauthn\Application\Model;
use D3\TestingTools\Production\IsMockable;
use OxidEsales\Eshop\Core\Language;
use Psr\Log\LoggerInterface;
class WebauthnErrors
{
@ -39,10 +38,6 @@ class WebauthnErrors
*/
public function translateError(string $msg, string $type = null): string
{
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug(
'error occured: '.$msg
);
$lang = d3GetOxidDIC()->get('d3ox.webauthn.'.Language::class);
$type = $type ? '_'.$type : null;

View File

@ -110,11 +110,10 @@ class WebauthnLogin
{
/** @var UtilsView $myUtilsView */
$myUtilsView = d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class);
/** @var d3_User_Webauthn $user */
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$userId = null;
try {
/** @var d3_User_Webauthn $user */
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$userId = $this->getUserId();
$this->handleErrorMessage();
@ -159,11 +158,10 @@ class WebauthnLogin
{
/** @var UtilsView $myUtilsView */
$myUtilsView = d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class);
/** @var d3_User_Webauthn $user */
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$userId = null;
try {
/** @var d3_User_Webauthn $user */
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$userId = $this->getUserId();
$this->handleErrorMessage();
@ -197,6 +195,8 @@ class WebauthnLogin
$user->logout();
$oStr = Str::getStr();
d3GetOxidDIC()->get('d3ox.webauthn.'.Config::class)->getActiveView()
->addTplParam('user', $oStr->htmlspecialchars($userId));
d3GetOxidDIC()->get('d3ox.webauthn.'.Config::class)->getActiveView()
->addTplParam('profile', $oStr->htmlspecialchars($selectedProfile));

View File

@ -20,15 +20,15 @@ $aLang = [
'charset' => 'UTF-8',
'PAGE_TITLE_D3WEBAUTHNLOGIN' => 'Passwortloses Anmelden',
'D3_WEBAUTHN_ACCOUNT' => 'Meine Anmeldeschlüssel',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'Meine Anmeldeschlüssel',
'D3_WEBAUTHN_ACCOUNT' => 'Meine Schlüssel',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'Meine Schlüssel',
'D3_WEBAUTHN_ACCOUNT_DESC' => 'Verwalten Sie hier Ihre Anmeldeschlüssel.',
'D3_WEBAUTHN_ACC_REGISTERNEW' => 'neue Registrierung erstellen',
'D3_WEBAUTHN_ACC_ADDKEY' => 'Anmeldeschlüssel hinzufügen',
'D3_WEBAUTHN_ACC_ADDKEY' => 'Sicherheitsschlüssel hinzufügen',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registrierte Anmeldeschlüssel',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registrierte Schlüssel',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Anmeldeschlüssel authentisieren.',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Hardwareschlüssel authentisieren.',
'WEBAUTHN_CANCEL_LOGIN' => 'Anmeldung abbrechen',
'D3_WEBAUTHN_BREADCRUMB' => 'Passwortloses Anmelden',
'D3_WEBAUTHN_CONFIRMATION' => 'Bestätigung erforderlich',
@ -37,15 +37,15 @@ $aLang = [
'D3_WEBAUTHN_DELETE' => 'Löschen',
'D3_WEBAUTHN_DELETE_CONFIRM' => 'Soll der Schlüssel wirklich gelöscht werden?',
'D3_WEBAUTHN_KEYNAME' => 'Name des Schlüssels',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'kein Anmeldeschlüssel registriert',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'kein Schlüssel registriert',
'D3_WEBAUTHN_ACCOUNT_TYPE0' => 'nur Passwort',
'D3_WEBAUTHN_ACCOUNT_TYPE1' => 'nur Auth-Schlüssel',
'D3_WEBAUTHN_ACCOUNT_TYPE2' => 'nur Auth-Sschlüssel, Passwort als Alternative',
'D3_WEBAUTHN_ACCOUNT_TYPE3' => 'Auth-Schlüssel und Passwort in Kombination',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Anmeldeschlüsseln ist nur bei lokalen oder gesicherten Verbindungen (https) möglich.',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Die Anmeldung mit Anmeldeschlüssel ist aus technischen Gründen derzeit leider nicht möglich. Bitte verwenden Sie statt dessen Ihr Passwort.',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Sicherheitsschlüsseln ist nur bei lokalen oder gesicherten Verbindungen (https) möglich.',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Die Anmeldung mit Sicherheitsschlüssel ist aus technischen Gründen derzeit leider nicht möglich. Bitte verwenden Sie statt dessen Ihr Passwort.',
'D3_WEBAUTHN_ERR_NOTLOADEDUSER' => "Kann keine Anmeldedaten von nicht geladenem Kundenkonto beziehen.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "Der Anmeldeschlüssel kann aus technischen Gründen nicht registriert werden. Bitte wenden Sie sich an den Shopbetreiber.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "Der Schlüssel kann aus technischen Gründen nicht registriert werden. Bitte wenden Sie sich an den Shopbetreiber.",
];

View File

@ -20,15 +20,15 @@ $aLang = [
'charset' => 'UTF-8',
'PAGE_TITLE_D3WEBAUTHNLOGIN' => 'Passwordless login',
'D3_WEBAUTHN_ACCOUNT' => 'My login keys',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'My login keys',
'D3_WEBAUTHN_ACCOUNT' => 'My keys',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'My keys',
'D3_WEBAUTHN_ACCOUNT_DESC' => 'Manage your login keys here.',
'D3_WEBAUTHN_ACC_REGISTERNEW' => 'create new registration',
'D3_WEBAUTHN_ACC_ADDKEY' => 'add login key',
'D3_WEBAUTHN_ACC_ADDKEY' => 'add security key',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registered login keys',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registered keys',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with login key.',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with hardware key.',
'WEBAUTHN_CANCEL_LOGIN' => 'Cancel login',
'D3_WEBAUTHN_BREADCRUMB' => 'Passwordless login',
'D3_WEBAUTHN_CONFIRMATION' => 'Confirmation required',
@ -37,15 +37,15 @@ $aLang = [
'D3_WEBAUTHN_DELETE' => 'delete',
'D3_WEBAUTHN_DELETE_CONFIRM' => 'Do you really want to delete the key?',
'D3_WEBAUTHN_KEYNAME' => 'name of the key',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'no login key registered',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'no key registered',
'D3_WEBAUTHN_ACCOUNT_TYPE0' => 'password only',
'D3_WEBAUTHN_ACCOUNT_TYPE1' => 'auth keys only',
'D3_WEBAUTHN_ACCOUNT_TYPE2' => 'auth keys only, password as an alternative',
'D3_WEBAUTHN_ACCOUNT_TYPE3' => 'auth key and password combined',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of login keys is only possible with local or secured connections (https).',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Unfortunately, logging in with a login key is currently not possible for technical reasons. Please use your password instead.',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of security keys is only possible with local or secured connections (https).',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Unfortunately, logging in with a security key is currently not possible for technical reasons. Please use your password instead.',
'D3_WEBAUTHN_ERR_NOTLOADEDUSER' => "Cannot obtain login data from unloaded customer account.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "The login key cannot be registered for technical reasons. Please contact the shop operator.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "The key cannot be registered for technical reasons. Please contact the shop operator.",
];

View File

@ -18,35 +18,35 @@ $sLangName = "Deutsch";
$aLang = [
'charset' => 'UTF-8',
'D3_WEBAUTHN_ERROR_UNVALID' => 'Der verwendete Anmeldeschlüssel ist ungültig oder kann nicht geprüft werden.',
'D3_WEBAUTHN_ERROR_UNVALID' => 'Der verwendete Schlüssel ist ungültig oder kann nicht geprüft werden.',
'D3_WEBAUTHN_ERROR_MISSINGPKC' => 'Keine prüfbaren Anfrageoptionen gespeichert. Bitte führen Sie die Anmeldung noch einmal durch bzw. wenden sich an den Betreiber.',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Anmeldeschlüssel authentisieren.',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Hardwareschlüssel authentisieren.',
'WEBAUTHN_CANCEL_LOGIN' => 'Anmeldung abbrechen',
'D3WEBAUTHN_CONF_BROWSER_REQUEST' => 'Bitte die Anfrage des Browsers bestätigen:',
'D3WEBAUTHN_CANCEL' => 'Abbrechen',
'D3WEBAUTHN_DELETE' => 'Löschen',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Soll der Anmeldeschlüssel wirklich gelöscht werden?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'kein Anmeldeschlüssel registriert',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Soll der Schlüssel wirklich gelöscht werden?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'kein Schlüssel registriert',
'd3mxuser_webauthn' => 'Anmeldeschlüssel',
'd3mxuser_webauthn' => 'Hardwareschlüssel',
'D3_WEBAUTHN_REGISTERNEW' => 'neue Registrierung erstellen',
'D3_WEBAUTHN_ADDKEY' => 'Anmeldeschlüssel hinzufügen',
'D3_WEBAUTHN_ADDKEY' => 'Sicherheitsschlüssel hinzufügen',
'D3_WEBAUTHN_KEYNAME' => 'Name des Schlüssels',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registrierte Anmeldeschlüssel',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registrierte Schlüssel',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Anmeldeschlüsseln ist nur bei lokalen oder gesicherten Verbindungen (https) möglich.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_CREATE => 'Der AnmeldeSchlüssel kann nicht oder nicht mehr verwendet werden. Möglicherweise wurde dieser in Ihrem Konto schon einmal gespeichert.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_GET => 'Der Anmeldeschlüssel kann nicht validiert werden.',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Sicherheitsschlüsseln ist nur bei lokalen oder gesicherten Verbindungen (https) möglich.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_CREATE => 'Der Schlüssel vom Token kann nicht oder nicht mehr verwendet werden. Möglicherweise wurde dieser in Ihrem Konto schon einmal gespeichert.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_GET => 'Der Schlüssel kann nicht validiert werden.',
'D3_WEBAUTHN_ERR_NOTALLOWED' => 'Die Anfrage wurde vom Browser oder der Plattform nicht zugelassen. Möglicherweise fehlen Berechtigungen oder die Zeit ist abgelaufen.',
'D3_WEBAUTHN_ERR_ABORT' => 'Die Aktion wurde vom Browser oder der Plattform abgebrochen.',
'D3_WEBAUTHN_ERR_CONSTRAINT' => 'Die Aktion konnte vom authentisierenden Gerät nicht durchgeführt werden.',
'D3_WEBAUTHN_ERR_NOTSUPPORTED' => 'Die Aktion wird nicht unterstützt.',
'D3_WEBAUTHN_ERR_UNKNOWN' => 'Die Aktion wurde wegen eines unbekannten Fehlers abgebrochen.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Ihr Browser unterstützt die Verwendung von Anmeldeschlüsseln leider nicht.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Ihr Browser unterstützt die Verwendung von Hardwareschlüsseln leider nicht.',
'D3_WEBAUTHN_ERR_TECHNICALERROR' => 'Beim Prüfen der Zugangsdaten ist ein technischer Fehler aufgetreten.',
'D3_WEBAUTHN_ERR_NOTLOADEDUSER' => "Kann keine Anmeldedaten von nicht geladenem Kundenkonto beziehen.",
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Die Anmeldung mit Anmeldeschlüssel ist aus technischen Gründen derzeit leider nicht möglich. Bitte verwenden Sie statt dessen Ihr Passwort.',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Die Anmeldung mit Sicherheitsschlüssel ist aus technischen Gründen derzeit leider nicht möglich. Bitte verwenden Sie statt dessen Ihr Passwort.',
];

View File

@ -18,35 +18,35 @@ $sLangName = "English";
$aLang = [
'charset' => 'UTF-8',
'D3_WEBAUTHN_ERROR_UNVALID' => 'The used login key is invalid or cannot be checked.',
'D3_WEBAUTHN_ERROR_UNVALID' => 'The key used is invalid or cannot be checked.',
'D3_WEBAUTHN_ERROR_MISSINGPKC' => 'No verifiable request options saved. Please perform the registration again or contact the operator.',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with login key.',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with hardware key.',
'WEBAUTHN_CANCEL_LOGIN' => 'Cancel login',
'D3WEBAUTHN_CONF_BROWSER_REQUEST' => 'Please confirm the browser request:',
'D3WEBAUTHN_CANCEL' => 'Cancel',
'D3WEBAUTHN_DELETE' => 'Delete',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Do you really want to delete the login key?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'No login key registered',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Do you really want to delete the key?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'No key registered',
'd3mxuser_webauthn' => 'login keys',
'd3mxuser_webauthn' => 'hardware key',
'D3_WEBAUTHN_REGISTERNEW' => 'create new registration',
'D3_WEBAUTHN_ADDKEY' => 'add login key',
'D3_WEBAUTHN_ADDKEY' => 'add security key',
'D3_WEBAUTHN_KEYNAME' => 'Key name',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered login keys',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered keys',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of login keys is only possible with local or secure connections (https).',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_CREATE => 'The login key from the token cannot be used or can no longer be used. It may have been stored in your account before.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_GET => 'The login key cannot be validated.',
'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of security keys is only possible with local or secure connections (https).',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_CREATE => 'The key from the token cannot be used or can no longer be used. It may have been stored in your account before.',
'D3_WEBAUTHN_ERR_INVALIDSTATE_'.WebauthnConf::TYPE_GET => 'The key cannot be validated.',
'D3_WEBAUTHN_ERR_NOTALLOWED' => 'The request was not allowed by the browser or the platform. Possibly permissions are missing or the time has expired.',
'D3_WEBAUTHN_ERR_ABORT' => 'The action was aborted by the browser or the platform.',
'D3_WEBAUTHN_ERR_CONSTRAINT' => 'The action could not be performed by the authenticating device.',
'D3_WEBAUTHN_ERR_NOTSUPPORTED' => 'The action is not supported.',
'D3_WEBAUTHN_ERR_UNKNOWN' => 'The action was cancelled due to an unknown error.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Unfortunately, your browser does not support the use of login keys.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Unfortunately, your browser does not support the use of hardware keys.',
'D3_WEBAUTHN_ERR_TECHNICALERROR' => 'A technical error occurred while checking the access data.',
'D3_WEBAUTHN_ERR_NOTLOADEDUSER' => "Can't create webauthn user entity from not loaded user",
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Unfortunately, logging in with a login key is currently not possible for technical reasons. Please use your password instead.',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Unfortunately, logging in with a security key is currently not possible for technical reasons. Please use your password instead.',
];

View File

@ -135,11 +135,4 @@ services:
- 'getDb'
arguments:
- 2
shared: true
d3ox.webauthn.OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder:
class: 'OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder'
factory: 'oxNew'
arguments:
- 'OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder'
shared: false
shared: true

View File

@ -17,7 +17,6 @@ namespace D3\Webauthn\Modules\Application\Component;
use Assert\Assert;
use Assert\AssertionFailedException;
use Assert\InvalidArgumentException;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Model\Exceptions\WebauthnGetException;
use D3\Webauthn\Application\Model\Exceptions\WebauthnLoginErrorException;
@ -66,27 +65,29 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent
$user = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$userId = $user->d3GetLoginUserId($lgn_user);
if ($this->d3CanUseWebauthn($lgn_user, $userId) && $this->d3HasWebauthnButNotLoggedin($userId)) {
$session = d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS,
$this->getClassKey() != 'd3webauthnlogin' ? $this->getClassKey() : 'start'
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER,
$userId
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_NAVPARAMS,
$this->getParent()->getNavigationParams()
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS,
$this->getParent()->getViewConfig()->getNavFormParams()
);
if ($this->d3CanUseWebauthn($lgn_user, $userId)) {
if ($this->d3HasWebauthnButNotLoggedin($userId)) {
$session = d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS,
$this->getClassKey() != 'd3webauthnlogin' ? $this->getClassKey() : 'start'
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER,
$userId
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_NAVPARAMS,
$this->getParent()->getNavigationParams()
);
$session->setVariable(
WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS,
$this->getParent()->getViewConfig()->getNavFormParams()
);
$sUrl = d3GetOxidDIC()->get('d3ox.webauthn.'.Config::class)->getShopHomeUrl() . 'cl=d3webauthnlogin';
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect($sUrl);
$sUrl = d3GetOxidDIC()->get('d3ox.webauthn.'.Config::class)->getShopHomeUrl() . 'cl=d3webauthnlogin';
d3GetOxidDIC()->get('d3ox.webauthn.'.Utils::class)->redirect($sUrl);
}
}
}
@ -162,7 +163,6 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent
/**
* @return WebauthnLogin
* @throws InvalidArgumentException
*/
protected function d3GetWebauthnLogin(): WebauthnLogin
{
@ -172,7 +172,8 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent
$credential = $request->getRequestEscapedParameter('credential');
$error = $request->getRequestEscapedParameter('error');
Assert::that($credential)->string('credential value expected to be string');
Assert::that($credential)->string('credential value expected to be string')
->notEmpty('credential value expected contained content');
Assert::that($error)->string('error value expected to be string');
return oxNew(WebauthnLogin::class, $credential, $error);

View File

@ -18,10 +18,12 @@ namespace D3\Webauthn\Setup;
use D3\TestingTools\Production\IsMockable;
use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
use Exception;
use OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder;
use OxidEsales\Eshop\Application\Controller\FrontendController;
use OxidEsales\Eshop\Core\Config;
use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface;
use OxidEsales\Eshop\Core\DbMetaDataHandler;
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
use OxidEsales\Eshop\Core\Exception\DatabaseErrorException;
use OxidEsales\Eshop\Core\SeoEncoder;
use OxidEsales\Eshop\Core\Utils;
use OxidEsales\Eshop\Core\UtilsView;
@ -37,25 +39,97 @@ use Psr\Log\LoggerInterface;
class Actions
{
use IsMockable;
public const FIELDLENGTH_CREDID = 512;
public const FIELDLENGTH_CREDENTIAL = 2000;
public $seo_de = 'anmeldeschluessel';
public $seo_en = 'en/login-keys';
public $seo_de = 'sicherheitsschluessel';
public $seo_en = 'en/key-authentication';
public $stdClassName = 'd3_account_webauthn';
/**
* @throws Exception
* SQL statement, that will be executed only at the first time of module installation.
*
* @var string
*/
public function runModuleMigrations()
protected $createCredentialSql =
"CREATE TABLE `d3wa_usercredentials` (
`OXID` char(32) NOT NULL,
`OXUSERID` char(32) NOT NULL,
`OXSHOPID` int(11) NOT NULL,
`NAME` varchar(100) NOT NULL,
`CREDENTIALID` varchar(".self::FIELDLENGTH_CREDID.") NOT NULL,
`CREDENTIAL` varchar(".self::FIELDLENGTH_CREDENTIAL.") NOT NULL,
`OXTIMESTAMP` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`OXID`),
KEY `CREDENTIALID_IDX` (`CREDENTIALID`),
KEY `SHOPUSER_IDX` (`OXUSERID`,`OXSHOPID`) USING BTREE
) ENGINE=InnoDB COMMENT='WebAuthn Credentials';";
/**
* Execute the sql at the first time of the module installation.
* @return void
* @throws DatabaseConnectionException
* @throws DatabaseErrorException
*/
public function setupModule()
{
/** @var MigrationsBuilder $migrationsBuilder */
$migrationsBuilder = d3GetOxidDIC()->get('d3ox.webauthn.'.MigrationsBuilder::class);
$migrations = $migrationsBuilder->build();
$migrations->execute('migrations:migrate', 'd3webauthn');
if (!$this->tableExists('d3wa_usercredentials')) {
$this->executeSQL($this->createCredentialSql);
}
}
/**
* Check if table exists
*
* @param string $sTableName table name
*
* @return bool
*/
public function tableExists(string $sTableName): bool
{
$oDbMetaDataHandler = d3GetOxidDIC()->get('d3ox.webauthn.'.DbMetaDataHandler::class);
return $oDbMetaDataHandler->tableExists($sTableName);
}
/**
* @return DatabaseInterface|null
* @throws DatabaseConnectionException
*/
protected function d3GetDb(): ?DatabaseInterface
{
/** @var DatabaseInterface $db */
$db = d3GetOxidDIC()->get('d3ox.webauthn.'.DatabaseInterface::class.'.assoc');
return $db;
}
/**
* Executes given sql statement.
*
* @param string $sSQL Sql to execute.
* @throws DatabaseConnectionException
* @throws DatabaseErrorException
*/
public function executeSQL(string $sSQL)
{
$this->d3GetDb()->execute($sSQL);
}
/**
* Check if field exists in table
*
* @param string $sFieldName field name
* @param string $sTableName table name
*
* @return bool
*/
public function fieldExists(string $sFieldName, string $sTableName): bool
{
$oDbMetaDataHandler = d3GetOxidDIC()->get('d3ox.webauthn.'.DbMetaDataHandler::class);
return $oDbMetaDataHandler->fieldExists($sFieldName, $sTableName);
}
/**
* Regenerate views for changed tables
* @throws Exception
*/
public function regenerateViews()
{
@ -65,7 +139,6 @@ class Actions
/**
* clear cache
* @throws Exception
*/
public function clearCache()
{
@ -131,7 +204,6 @@ class Actions
/**
* @return void
* @throws Exception
*/
public function seoUrl()
{
@ -148,11 +220,9 @@ class Actions
/**
* @return bool
* @throws Exception
*/
public function hasSeoUrl(): bool
{
/** @var SeoEncoder $seoEncoder */
$seoEncoder = d3GetOxidDIC()->get('d3ox.webauthn.'.SeoEncoder::class);
$seoUrl = $seoEncoder->getStaticUrl(
d3GetOxidDIC()->get('d3ox.webauthn.'.FrontendController::class)->getViewConfig()->getSelfLink() .
@ -164,7 +234,6 @@ class Actions
/**
* @return void
* @throws Exception
*/
public function createSeoUrl()
{

View File

@ -34,7 +34,7 @@ class Events
}
$actions = oxNew(Actions::class);
$actions->runModuleMigrations();
$actions->setupModule();
$actions->regenerateViews();
$actions->clearCache();
$actions->seoUrl();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -66,15 +66,14 @@ $logo = '<img src="https://logos.oxidmodule.com/d3logo.svg" alt="(D3)" style="he
$aModule = [
'id' => $sModuleId,
'title' => [
'de' => $logo.' zweiter Faktor - Passwortlose Anmeldung mit passkeys',
'en' => $logo.' second factor - passwordless login with passkeys',
'de' => $logo.' zweiter Faktor - Passwortlose Anmeldung',
'en' => $logo.' second factor - passwordless login',
],
'description' => [
'de' => 'Passwortlose Anmeldung f&uuml;r OXID eSales Shop (mit WebAuthn / FIDO2 basierten passkeys)',
'en' => 'Passwordless login for OXID eSales shop (with WebAuthn / FIDO2 based passkeys)',
'de' => 'Passwortlose Anmeldung f&uuml;r OXID eSales Shop (WebAuthn / FIDO2 basiert)',
'en' => 'Passwordless login for OXID eSales shop (WebAuthn / FIDO2 based)',
],
'version' => '1.0.0.0',
'thumbnail' => 'logo.png',
'author' => 'D&sup3; Data Development (Inh.: Thomas Dartsch)',
'email' => 'support@shopmodule.com',
'url' => 'https://www.oxidmodule.com/',

View File

@ -1,112 +0,0 @@
<?php
/**
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* https://www.d3data.de
*
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
* @link https://www.oxidmodule.com
*/
declare(strict_types=1);
namespace D3\Webauthn\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\DateTimeType;
use Doctrine\DBAL\Types\IntegerType;
use Doctrine\DBAL\Types\StringType;
use Doctrine\Migrations\AbstractMigration;
final class Version20230209212939 extends AbstractMigration
{
public const FIELDLENGTH_CREDID = 512;
public const FIELDLENGTH_CREDENTIAL = 2000;
public function getDescription() : string
{
return 'create credential database table';
}
public function up(Schema $schema) : void
{
$this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
$table = !$schema->hasTable('d3wa_usercredentials') ?
$schema->createTable('d3wa_usercredentials') :
$schema->getTable('d3wa_usercredentials');
if (!$table->hasColumn('OXID')) {
$table->addColumn('OXID', (new StringType())->getName())
->setLength(32)
->setFixed(true)
->setNotnull(true);
}
if (!$table->hasColumn('OXUSERID')) {
$table->addColumn('OXUSERID', (new StringType())->getName())
->setLength(32)
->setFixed(true)
->setNotnull(true);
}
if (!$table->hasColumn('OXSHOPID')) {
$table->addColumn('OXSHOPID', (new IntegerType())->getName())
->setLength(11)
->setNotnull(true);
}
if (!$table->hasColumn('NAME')) {
$table->addColumn('NAME', (new StringType())->getName())
->setLength(100)
->setFixed(false)
->setNotnull(true);
}
if (!$table->hasColumn('CREDENTIALID')) {
$table->addColumn('CREDENTIALID', (new StringType())->getName())
->setLength(self::FIELDLENGTH_CREDID)
->setFixed(false)
->setNotnull(true);
}
if (!$table->hasColumn('CREDENTIAL')) {
$table->addColumn('CREDENTIAL', (new StringType())->getName())
->setLength(self::FIELDLENGTH_CREDENTIAL)
->setFixed(false)
->setNotnull(true);
}
if (!$table->hasColumn('OXTIMESTAMP')) {
$table->addColumn('OXTIMESTAMP', (new DateTimeType())->getName())
->setType(new DateTimeType())
->setNotnull(true)
// can't set default value via default method
->setColumnDefinition('timestamp DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP');
}
if (!$table->hasPrimaryKey()) {
$table->setPrimaryKey(['OXID']);
}
if (!$table->hasIndex('SHOPUSER_IDX')) {
$table->addIndex(['OXUSERID', 'OXSHOPID'], 'SHOPUSER_IDX');
}
if (!$table->hasIndex('CREDENTIALID_IDX')) {
$table->addIndex(['CREDENTIALID'], 'CREDENTIALID_IDX');
}
$table->setComment('WebAuthn Credentials');
}
public function down(Schema $schema) : void
{
if ($schema->hasTable('d3wa_usercredentials')) {
$schema->dropTable('d3wa_usercredentials');
}
}
}

View File

@ -1,4 +0,0 @@
name: D3 Twofactor Passwordless
migrations_namespace: D3\Webauthn\Migrations
table_name: d3wa_migrations
migrations_directory: data

View File

@ -179,7 +179,7 @@ const requestCredentials = (publicKey) => {
response: {
authenticatorData: base64ArrayBuffer(authenticateInfo.response.authenticatorData),
signature: base64ArrayBuffer(authenticateInfo.response.signature),
userHandle: base64ArrayBuffer(authenticateInfo.response.userHandle),
userHandle: authenticateInfo.response.userHandle,
clientDataJSON: base64ArrayBuffer(authenticateInfo.response.clientDataJSON)
},
type: authenticateInfo.type

View File

@ -1,14 +0,0 @@
<?php
/**
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* https://www.d3data.de
*
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
* @link https://www.oxidmodule.com
*/
const D3WEBAUTHN_REQUIRE_MODCFG = false;

View File

@ -17,15 +17,15 @@
namespace D3\Webauthn\tests\integration;
use D3\DIContainerHandler\d3DicHandler;
use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler;
use D3\ModCfg\Tests\unit\d3ModCfgUnitTestCase;
use Exception;
use OxidEsales\Eshop\Application\Model\Article;
use OxidEsales\Eshop\Application\Model\Rights;
use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\Eshop\Core\Model\BaseModel;
use OxidEsales\TestingLibrary\UnitTestCase;
abstract class integrationTestCase extends UnitTestCase
abstract class integrationTestCase extends d3ModCfgUnitTestCase
{
/**
* Set up fixture.

View File

@ -76,7 +76,7 @@ class passwordFrontendAuthTest extends integrationTestCase
* @test
* @dataProvider loginDataProvider
*/
public function testCheckLoginReturn($username, $password, $expected, $redirect = null)
public function testCheckLoginReturn($username, $password, $expected)
{
$_POST['lgn_usr'] = $username;
$_POST['lgn_pwd'] = $password;

View File

@ -1,167 +0,0 @@
<?php
/**
* This Software is the property of Data Development and is protected
* by copyright law - it is NOT Freeware.
* Any unauthorized use of this software without a valid license
* is a violation of the license agreement and will be prosecuted by
* civil and criminal law.
* http://www.shopmodule.com
*
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <support@shopmodule.com>
* @link http://www.oxidmodule.com
*/
namespace D3\Webauthn\tests\integration;
use D3\DIContainerHandler\d3DicHandler;
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
use OxidEsales\Eshop\Core\Utils;
class webauthnFrontendAuthTest extends passwordFrontendAuthTest
{
protected $userList = [
1 => 'userId1',
2 => 'userId2',
3 => 'userId3',
4 => 'userId4',
5 => 'userId5',
];
protected $credentialList = [
1 => 'credId1',
2 => 'credId2',
3 => 'credId3',
4 => 'credId4',
5 => 'credId5',
];
public function createTestData()
{
parent::createTestData();
$this->createUser(
$this->userList[5],
[
'oxactive' => 1,
'oxrights' => 'malladmin',
'oxshopid' => 1,
'oxusername' => 'wawrongshopid@user.localhost',
'oxpassword' => '$2y$10$QErMJNHQCoN03tfCUQDRfOvbwvqfzwWw1iI/7bC49fKQrPKoDdnaK', // 123456
'oxstreet' => __CLASS__,
],
true
);
$this->createObject(
PublicKeyCredential::class,
$this->credentialList[1],
[
'oxuserid' => $this->userList[1],
'oxshopid' => 1,
'name' => __CLASS__,
'credentialid' => 'ITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAAA==',
'credential'=> 'TzozNDoiV2ViYXV0aG5cUHVibGljS2V5Q3JlZGVudGlhbFNvdXJjZSI6MTA6e3M6MjQ6IgAqAHB1YmxpY0tleUNyZWRlbnRpYWxJZCI7czo3MDoiITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAACI7czo3OiIAKgB0eXBlIjtzOjEwOiJwdWJsaWMta2V5IjtzOjEzOiIAKgB0cmFuc3BvcnRzIjthOjA6e31zOjE4OiIAKgBhdHRlc3RhdGlvblR5cGUiO3M6NDoibm9uZSI7czoxMjoiACoAdHJ1c3RQYXRoIjtPOjMzOiJXZWJhdXRoblxUcnVzdFBhdGhcRW1wdHlUcnVzdFBhdGgiOjA6e31zOjk6IgAqAGFhZ3VpZCI7TzozNToiUmFtc2V5XFV1aWRcTGF6eVxMYXp5VXVpZEZyb21TdHJpbmciOjE6e3M6Njoic3RyaW5nIjtzOjM2OiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiO31zOjIyOiIAKgBjcmVkZW50aWFsUHVibGljS2V5IjtzOjc3OiKlAQIDJiABIVggHucXfQh0acwpsffVRM02F7P57mVm6hPX/l8Pjbh0jOwiWCBRT5MMqa909tcXHqG/EKfjXXDd9UEisk+ZF7QSTfwv0CI7czoxMzoiACoAdXNlckhhbmRsZSI7czoxNDoib3hkZWZhdWx0YWRtaW4iO3M6MTA6IgAqAGNvdW50ZXIiO2k6NDI3MTtzOjEwOiIAKgBvdGhlclVJIjtOO30=',
]
);
$this->createObject(
PublicKeyCredential::class,
$this->credentialList[2],
[
'oxuserid' => $this->userList[2],
'oxshopid' => 1,
'name' => __CLASS__,
'credentialid' => 'ITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAAA==',
'credential'=> 'TzozNDoiV2ViYXV0aG5cUHVibGljS2V5Q3JlZGVudGlhbFNvdXJjZSI6MTA6e3M6MjQ6IgAqAHB1YmxpY0tleUNyZWRlbnRpYWxJZCI7czo3MDoiITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAACI7czo3OiIAKgB0eXBlIjtzOjEwOiJwdWJsaWMta2V5IjtzOjEzOiIAKgB0cmFuc3BvcnRzIjthOjA6e31zOjE4OiIAKgBhdHRlc3RhdGlvblR5cGUiO3M6NDoibm9uZSI7czoxMjoiACoAdHJ1c3RQYXRoIjtPOjMzOiJXZWJhdXRoblxUcnVzdFBhdGhcRW1wdHlUcnVzdFBhdGgiOjA6e31zOjk6IgAqAGFhZ3VpZCI7TzozNToiUmFtc2V5XFV1aWRcTGF6eVxMYXp5VXVpZEZyb21TdHJpbmciOjE6e3M6Njoic3RyaW5nIjtzOjM2OiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiO31zOjIyOiIAKgBjcmVkZW50aWFsUHVibGljS2V5IjtzOjc3OiKlAQIDJiABIVggHucXfQh0acwpsffVRM02F7P57mVm6hPX/l8Pjbh0jOwiWCBRT5MMqa909tcXHqG/EKfjXXDd9UEisk+ZF7QSTfwv0CI7czoxMzoiACoAdXNlckhhbmRsZSI7czoxNDoib3hkZWZhdWx0YWRtaW4iO3M6MTA6IgAqAGNvdW50ZXIiO2k6NDI3MTtzOjEwOiIAKgBvdGhlclVJIjtOO30=',
]
);
$this->createObject(
PublicKeyCredential::class,
$this->credentialList[3],
[
'oxuserid' => $this->userList[3],
'oxshopid' => 1,
'name' => __CLASS__,
'credentialid' => 'ITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAAA==',
'credential'=> 'TzozNDoiV2ViYXV0aG5cUHVibGljS2V5Q3JlZGVudGlhbFNvdXJjZSI6MTA6e3M6MjQ6IgAqAHB1YmxpY0tleUNyZWRlbnRpYWxJZCI7czo3MDoiITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAACI7czo3OiIAKgB0eXBlIjtzOjEwOiJwdWJsaWMta2V5IjtzOjEzOiIAKgB0cmFuc3BvcnRzIjthOjA6e31zOjE4OiIAKgBhdHRlc3RhdGlvblR5cGUiO3M6NDoibm9uZSI7czoxMjoiACoAdHJ1c3RQYXRoIjtPOjMzOiJXZWJhdXRoblxUcnVzdFBhdGhcRW1wdHlUcnVzdFBhdGgiOjA6e31zOjk6IgAqAGFhZ3VpZCI7TzozNToiUmFtc2V5XFV1aWRcTGF6eVxMYXp5VXVpZEZyb21TdHJpbmciOjE6e3M6Njoic3RyaW5nIjtzOjM2OiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiO31zOjIyOiIAKgBjcmVkZW50aWFsUHVibGljS2V5IjtzOjc3OiKlAQIDJiABIVggHucXfQh0acwpsffVRM02F7P57mVm6hPX/l8Pjbh0jOwiWCBRT5MMqa909tcXHqG/EKfjXXDd9UEisk+ZF7QSTfwv0CI7czoxMzoiACoAdXNlckhhbmRsZSI7czoxNDoib3hkZWZhdWx0YWRtaW4iO3M6MTA6IgAqAGNvdW50ZXIiO2k6NDI3MTtzOjEwOiIAKgBvdGhlclVJIjtOO30=',
]
);
$this->createObject(
PublicKeyCredential::class,
$this->credentialList[4],
[
'oxuserid' => $this->userList[4],
'oxshopid' => 1,
'name' => __CLASS__,
'credentialid' => 'ITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAAA==',
'credential'=> 'TzozNDoiV2ViYXV0aG5cUHVibGljS2V5Q3JlZGVudGlhbFNvdXJjZSI6MTA6e3M6MjQ6IgAqAHB1YmxpY0tleUNyZWRlbnRpYWxJZCI7czo3MDoiITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAACI7czo3OiIAKgB0eXBlIjtzOjEwOiJwdWJsaWMta2V5IjtzOjEzOiIAKgB0cmFuc3BvcnRzIjthOjA6e31zOjE4OiIAKgBhdHRlc3RhdGlvblR5cGUiO3M6NDoibm9uZSI7czoxMjoiACoAdHJ1c3RQYXRoIjtPOjMzOiJXZWJhdXRoblxUcnVzdFBhdGhcRW1wdHlUcnVzdFBhdGgiOjA6e31zOjk6IgAqAGFhZ3VpZCI7TzozNToiUmFtc2V5XFV1aWRcTGF6eVxMYXp5VXVpZEZyb21TdHJpbmciOjE6e3M6Njoic3RyaW5nIjtzOjM2OiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiO31zOjIyOiIAKgBjcmVkZW50aWFsUHVibGljS2V5IjtzOjc3OiKlAQIDJiABIVggHucXfQh0acwpsffVRM02F7P57mVm6hPX/l8Pjbh0jOwiWCBRT5MMqa909tcXHqG/EKfjXXDd9UEisk+ZF7QSTfwv0CI7czoxMzoiACoAdXNlckhhbmRsZSI7czoxNDoib3hkZWZhdWx0YWRtaW4iO3M6MTA6IgAqAGNvdW50ZXIiO2k6NDI3MTtzOjEwOiIAKgBvdGhlclVJIjtOO30=',
]
);
$this->createObject(
PublicKeyCredential::class,
$this->credentialList[5],
[
'oxuserid' => $this->userList[5],
'oxshopid' => 2,
'name' => __CLASS__,
'credentialid' => 'ITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAAA==',
'credential'=> 'TzozNDoiV2ViYXV0aG5cUHVibGljS2V5Q3JlZGVudGlhbFNvdXJjZSI6MTA6e3M6MjQ6IgAqAHB1YmxpY0tleUNyZWRlbnRpYWxJZCI7czo3MDoiITSNkDRdN1bfRrb9MDCNOfBNay7YqT3ZxWxxqIQWVvwN0tFOG7SN2JiCfcUfPMBhE9bTLU1Gbb/8+5eHyFR2d5DCrxAAACI7czo3OiIAKgB0eXBlIjtzOjEwOiJwdWJsaWMta2V5IjtzOjEzOiIAKgB0cmFuc3BvcnRzIjthOjA6e31zOjE4OiIAKgBhdHRlc3RhdGlvblR5cGUiO3M6NDoibm9uZSI7czoxMjoiACoAdHJ1c3RQYXRoIjtPOjMzOiJXZWJhdXRoblxUcnVzdFBhdGhcRW1wdHlUcnVzdFBhdGgiOjA6e31zOjk6IgAqAGFhZ3VpZCI7TzozNToiUmFtc2V5XFV1aWRcTGF6eVxMYXp5VXVpZEZyb21TdHJpbmciOjE6e3M6Njoic3RyaW5nIjtzOjM2OiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAiO31zOjIyOiIAKgBjcmVkZW50aWFsUHVibGljS2V5IjtzOjc3OiKlAQIDJiABIVggHucXfQh0acwpsffVRM02F7P57mVm6hPX/l8Pjbh0jOwiWCBRT5MMqa909tcXHqG/EKfjXXDd9UEisk+ZF7QSTfwv0CI7czoxMzoiACoAdXNlckhhbmRsZSI7czoxNDoib3hkZWZhdWx0YWRtaW4iO3M6MTA6IgAqAGNvdW50ZXIiO2k6NDI3MTtzOjEwOiIAKgBvdGhlclVJIjtOO30=',
]
);
}
public function cleanTestData()
{
parent::cleanTestData();
$this->deleteUser($this->userList[5]);
$this->deleteObject(PublicKeyCredential::class, $this->credentialList[1]);
$this->deleteObject(PublicKeyCredential::class, $this->credentialList[2]);
$this->deleteObject(PublicKeyCredential::class, $this->credentialList[3]);
$this->deleteObject(PublicKeyCredential::class, $this->credentialList[4]);
$this->deleteObject(PublicKeyCredential::class, $this->credentialList[5]);
}
/**
* @test
* @param $username
* @param $password
* @param $expected
* @param $redirect
* @return void
* @dataProvider loginDataProvider
*/
public function testCheckLoginReturn($username, $password, $expected, $redirect = null)
{
$utilsMock = $this->getMockBuilder(Utils::class)
->onlyMethods(['redirect'])
->getMock();
$utilsMock->expects($redirect ?: $this->never())->method('redirect')->willReturn(true);
d3DicHandler::getInstance()->set('d3ox.webauthn.'.Utils::class, $utilsMock);
parent::testCheckLoginReturn($username, $password, $expected);
}
/**
* @return array[]
*/
public function loginDataProvider(): array
{
return [
'not existing account' => ['unknown@user.localhost', '123456', 'user'],
'missing password' => ['noadmin@user.localhost', null, 'user', $this->once()],
'inactive account' => ['inactive@user.localhost', '123456', 'user'],
'wrong shop account' => ['wrongshop@user.localhost', '123456', 'user'],
'account ok' => ['admin@user.localhost', '123456', 'user'],
'cred. wrong shopid' => ['wawrongshopid@user.localhost', null, 'user'],
'credpass. wrong shopid'=> ['wawrongshopid@user.localhost', '123456', 'payment'],
];
}
}

View File

@ -15,7 +15,6 @@
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../Application</directory>
<directory suffix=".php">../migration</directory>
<directory suffix=".php">../Modules</directory>
<directory suffix=".php">../Setup</directory>
<exclude>

View File

@ -13,7 +13,6 @@
namespace D3\Webauthn\tests\unit\Application\Controller\Admin;
use Assert\InvalidArgumentException;
use D3\TestingTools\Development\CanAccessRestricted;
use D3\TestingTools\Production\IsMockable;
use D3\Webauthn\Application\Controller\Admin\d3user_webauthn;
@ -173,7 +172,7 @@ class d3user_webauthnTest extends WAUnitTestCase
])
->getMock();
$sutMock->expects($this->atLeastOnce())->method('setPageType');
$sutMock->expects($this->atLeastOnce())->method('setAuthnRegister')->willThrowException(new InvalidArgumentException('msg', 20));
$sutMock->expects($this->atLeastOnce())->method('setAuthnRegister')->willThrowException(oxNew(WebauthnException::class));
$this->callMethod(
$sutMock,

View File

@ -144,15 +144,11 @@ class d3webauthnadminloginTest extends d3webauthnloginTest
* @test
* @return void
* @throws ReflectionException
* @dataProvider \D3\Webauthn\tests\unit\Application\Controller\d3webauthnloginTest::generateCredentialRequestFailedDataProvider()
* @covers \D3\Webauthn\Application\Controller\Admin\d3webauthnadminlogin::generateCredentialRequest
*/
public function generateCredentialRequestFailed(
$exception,
$redirectClass = 'login',
$userVarName = WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER
) {
parent::generateCredentialRequestFailed($exception, $redirectClass, $userVarName);
public function generateCredentialRequestFailed($redirectClass = 'login', $userVarName = WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER)
{
parent::generateCredentialRequestFailed($redirectClass, $userVarName);
}
/**

View File

@ -187,7 +187,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
/** @var LoggerInterface|MockObject $loggerMock */
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class, [], '', true, true, true, ['error', 'debug']);
$loggerMock->expects($this->never())->method('error')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
$loggerMock->expects($this->never())->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var d3_account_webauthn|MockObject $oControllerMock */
@ -210,10 +210,9 @@ class d3_account_webauthnTest extends WAUnitTestCase
* @test
* @return void
* @throws ReflectionException
* @dataProvider canRequestNewCredentialCantGetCreationOptionsDataProvider
* @covers \D3\Webauthn\Application\Controller\d3_account_webauthn::requestNewCredential()
*/
public function canRequestNewCredentialCantGetCreationOptions($exception)
public function canRequestNewCredentialCantGetCreationOptions()
{
$oUser = oxNew(User::class);
$oUser->setId('foo');
@ -226,7 +225,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
/** @var LoggerInterface|MockObject $loggerMock */
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class, [], '', true, true, true, ['error', 'debug']);
$loggerMock->expects($this->atLeastOnce())->method('error')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
$loggerMock->expects($this->atLeastOnce())->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var d3_account_webauthn|MockObject $oControllerMock */
@ -234,7 +233,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
->onlyMethods(['setAuthnRegister', 'setPageType', 'getUser'])
->getMock();
$oControllerMock->expects($this->atLeastOnce())->method('setAuthnRegister')
->willThrowException($exception);
->willThrowException(oxNew(WebauthnException::class));
$oControllerMock->expects($this->never())->method('setPageType');
$oControllerMock->method('getUser')->willReturn($oUser);
@ -246,16 +245,6 @@ class d3_account_webauthnTest extends WAUnitTestCase
);
}
/**
* @return Generator
*/
public function canRequestNewCredentialCantGetCreationOptionsDataProvider(): Generator
{
yield 'WebauthnException' => [oxNew(WebauthnException::class)];
yield 'InvalidArgumentException' => [oxNew(InvalidArgumentException::class, 'msg', 20)];
}
/**
* @test
* @param $throwExc
@ -345,7 +334,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
/** @var LoggerInterface|MockObject $loggerMock */
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class, [], '', true, true, true, ['error', 'debug']);
$loggerMock->expects($this->once())->method('error')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
$loggerMock->expects($this->once())->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var User|MockObject $userMock */
@ -436,7 +425,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
/** @var LoggerInterface|MockObject $loggerMock */
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class, [], '', true, true, true, ['error', 'debug']);
$loggerMock->expects($this->once())->method('error')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
$loggerMock->expects($this->once())->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var User|MockObject $userMock */

View File

@ -15,7 +15,6 @@ declare(strict_types=1);
namespace D3\Webauthn\tests\unit\Application\Controller;
use Assert\InvalidArgumentException;
use D3\TestingTools\Development\CanAccessRestricted;
use D3\Webauthn\Application\Controller\d3webauthnlogin;
use D3\Webauthn\Application\Model\Exceptions\WebauthnException;
@ -145,7 +144,7 @@ class d3webauthnloginTest extends WAUnitTestCase
/** @var LoggerInterface|MockObject $loggerMock */
$loggerMock = $this->getMockForAbstractClass(LoggerInterface::class, [], '', true, true, true, ['error', 'debug']);
$loggerMock->expects($this->never())->method('error')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
$loggerMock->expects($this->never())->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var Session|MockObject $sessionMock */
@ -182,14 +181,9 @@ class d3webauthnloginTest extends WAUnitTestCase
* @test
* @return void
* @throws ReflectionException
* @dataProvider generateCredentialRequestFailedDataProvider
* @covers \D3\Webauthn\Application\Controller\d3webauthnlogin::generateCredentialRequest
*/
public function generateCredentialRequestFailed(
$exception,
$redirectClass = 'start',
$userVarName = WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER
)
public function generateCredentialRequestFailed($redirectClass = 'start', $userVarName = WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER)
{
$currUserFixture = 'currentUserFixture';
@ -215,7 +209,7 @@ class d3webauthnloginTest extends WAUnitTestCase
->onlyMethods(['getRequestOptions'])
->getMock();
$webAuthnMock->expects($this->once())->method('getRequestOptions')->with($currUserFixture)
->willThrowException($exception);
->willThrowException(oxNew(WebauthnException::class, 'foobar0'));
d3GetOxidDIC()->set(Webauthn::class, $webAuthnMock);
/** @var Utils|MockObject $utilsMock */
@ -239,15 +233,6 @@ class d3webauthnloginTest extends WAUnitTestCase
);
}
/**
* @return Generator
*/
public function generateCredentialRequestFailedDataProvider(): Generator
{
yield 'WebauthnException' => [oxNew(WebauthnException::class, 'foobar0')];
yield 'InvalidArgumentException' => [oxNew(InvalidArgumentException::class, 'foobar0', 20)];
}
/**
* @test
* @return void

View File

@ -141,8 +141,8 @@ class PublicKeyCredentialTest extends WAUnitTestCase
$this->canGetField(
'credentialid',
'getCredentialId',
base64_encode('credentialFixture'),
'credentialFixture'
'credentialFixture',
base64_decode('credentialFixture')
);
}

View File

@ -182,7 +182,7 @@ class WebauthnTest extends WAUnitTestCase
*/
public function canGetOptionsDataProvider(): Generator
{
yield 'json encoded' => [json_encode(['jsonstring'])];
yield 'json encoded' => ['jsonstring'];
yield 'json failed' => [false];
}

View File

@ -19,9 +19,9 @@ use D3\TestingTools\Development\CanAccessRestricted;
use D3\Webauthn\Setup\Actions;
use D3\Webauthn\tests\unit\WAUnitTestCase;
use Exception;
use OxidEsales\DoctrineMigrationWrapper\Migrations;
use OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder;
use OxidEsales\Eshop\Application\Controller\FrontendController;
use OxidEsales\Eshop\Core\Database\Adapter\DatabaseInterface;
use OxidEsales\Eshop\Core\Database\Adapter\Doctrine\Database;
use OxidEsales\Eshop\Core\DbMetaDataHandler;
use OxidEsales\Eshop\Core\Registry;
use OxidEsales\Eshop\Core\SeoEncoder;
@ -45,35 +45,141 @@ class ActionsTest extends WAUnitTestCase
/**
* @test
* @param $tableExist
* @param $expectedInvocation
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::runModuleMigrations()
* @covers \D3\Webauthn\Setup\Actions::setupModule
* @dataProvider canSetupModuleDataProvider
*/
public function canRunModuleMigrations()
public function canSetupModule($tableExist, $expectedInvocation)
{
/** @var Migrations|MockObject $migrationMock */
$migrationMock = $this->getMockBuilder(Migrations::class)
->onlyMethods(['execute'])
->disableOriginalConstructor()
/** @var Actions|MockObject $sut */
$sut = $this->getMockBuilder(Actions::class)
->onlyMethods(['tableExists', 'executeSQL'])
->getMock();
$migrationMock->expects($this->atLeastOnce())->method('execute')->with(
$this->identicalTo('migrations:migrate'),
$this->identicalTo('d3webauthn')
);
$sut->method('tableExists')->willReturn($tableExist);
$sut->expects($expectedInvocation)->method('executeSQL')->willReturn(true);
/** @var MigrationsBuilder|MockObject $migrationsBuilderMock */
$migrationsBuilderMock = $this->getMockBuilder(MigrationsBuilder::class)
->onlyMethods(['build'])
$this->callMethod(
$sut,
'setupModule'
);
}
/**
* @return array[]
*/
public function canSetupModuleDataProvider(): array
{
return [
'table exist' => [true, $this->never()],
'table not exist' => [false, $this->once()],
];
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::tableExists
*/
public function canCheckTableExists()
{
$expected = true;
/** @var DbMetaDataHandler|MockObject $DbMetaDataMock */
$DbMetaDataMock = $this->getMockBuilder(DbMetaDataHandler::class)
->onlyMethods(['tableExists'])
->getMock();
$migrationsBuilderMock->method('build')->willReturn($migrationMock);
d3GetOxidDIC()->set('d3ox.webauthn.'.MigrationsBuilder::class, $migrationsBuilderMock);
$DbMetaDataMock->expects($this->once())->method('tableExists')->willReturn($expected);
d3GetOxidDIC()->set('d3ox.webauthn.'.DbMetaDataHandler::class, $DbMetaDataMock);
/** @var Actions $sut */
$sut = oxNew(Actions::class);
$this->assertSame(
$expected,
$this->callMethod(
$sut,
'tableExists',
['testTable']
)
);
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::d3GetDb
*/
public function d3GetDbReturnsRightInstance()
{
$sut = oxNew(Actions::class);
$this->assertInstanceOf(
DatabaseInterface::class,
$this->callMethod(
$sut,
'd3GetDb'
)
);
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::executeSQL
*/
public function canExecuteSQL()
{
/** @var Database|MockObject $dbMock */
$dbMock = $this->getMockBuilder(Database::class)
->onlyMethods(['execute'])
->getMock();
$dbMock->expects($this->once())->method('execute');
$sut = $this->getMockBuilder(Actions::class)
->onlyMethods(['d3GetDb'])
->getMock();
$sut->method('d3GetDb')->willReturn($dbMock);
$this->callMethod(
$sut,
'runModuleMigrations'
'executeSQL',
['query']
);
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::fieldExists
*/
public function canCheckFieldExists()
{
$expected = true;
/** @var DbMetaDataHandler|MockObject $DbMetaDataMock */
$DbMetaDataMock = $this->getMockBuilder(DbMetaDataHandler::class)
->onlyMethods(['fieldExists'])
->getMock();
$DbMetaDataMock->expects($this->once())->method('fieldExists')->willReturn($expected);
d3GetOxidDIC()->set('d3ox.webauthn.'.DbMetaDataHandler::class, $DbMetaDataMock);
/** @var Actions $sut */
$sut = oxNew(Actions::class);
$this->assertSame(
$expected,
$this->callMethod(
$sut,
'fieldExists',
['testField', 'testTable']
)
);
}

View File

@ -1,303 +0,0 @@
<?php
/**
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* https://www.d3data.de
*
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
* @link https://www.oxidmodule.com
*/
declare(strict_types=1);
namespace D3\Webauthn\tests\unit\migration\data;
use D3\TestingTools\Development\CanAccessRestricted;
use D3\Webauthn\Migrations\Version20230209212939;
use D3\Webauthn\tests\unit\WAUnitTestCase;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQL57Platform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\MySqlSchemaManager;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Version\Version;
use Generator;
use PHPUnit\Framework\MockObject\MockObject;
use ReflectionException;
class Version20230209212939Test extends WAUnitTestCase
{
use CanAccessRestricted;
/** @var Version20230209212939 */
protected $sut;
public function setUp(): void
{
parent::setUp();
/** @var AbstractPlatform|MockObject $databasePlatformMock */
$databasePlatformMock = $this->getMockBuilder(MySQL57Platform::class)
->getMock();
/** @var AbstractSchemaManager|MockObject $schemaManagerMock */
$schemaManagerMock = $this->getMockBuilder(MySqlSchemaManager::class)
->disableOriginalConstructor()
->getMock();
/** @var Connection|MockObject $connectionMock */
$connectionMock = $this->getMockBuilder(Connection::class)
->disableOriginalConstructor()
->onlyMethods(['getDatabasePlatform', 'getSchemaManager'])
->getMock();
$connectionMock->method('getDatabasePlatform')->willReturn($databasePlatformMock);
$connectionMock->method('getSchemaManager')->willReturn($schemaManagerMock);
/** @var Configuration|MockObject $configurationMock */
$configurationMock = $this->getMockBuilder(Configuration::class)
->disableOriginalConstructor()
->onlyMethods(['getConnection'])
->getMock();
$configurationMock->method('getConnection')->willReturn($connectionMock);
/** @var Version|MockObject $versionMock */
$versionMock = $this->getMockBuilder(Version::class)
->onlyMethods(['getConfiguration'])
->disableOriginalConstructor()
->getMock();
$versionMock->method('getConfiguration')->willReturn($configurationMock);
$this->sut = oxNew(Version20230209212939::class, $versionMock);
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::getDescription
*/
public function canGetDescription()
{
$this->assertIsString(
$this->callMethod(
$this->sut,
'getDescription'
)
);
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::up
* @dataProvider canUpTableDataProvider
*/
public function canUpTable($tableExist, $invocationCount)
{
/** @var Table|MockObject $tableMock */
$tableMock = $this->getMockBuilder(Table::class)
->onlyMethods(['hasColumn', 'hasPrimaryKey', 'hasIndex'])
->disableOriginalConstructor()
->getMock();
$tableMock->method('hasColumn')->willReturn(true);
$tableMock->method('hasPrimaryKey')->willReturn(true);
$tableMock->method('hasIndex')->willReturn(true);
/** @var Schema|MockObject $schemaMock */
$schemaMock = $this->getMockBuilder(Schema::class)
->onlyMethods(['hasTable', 'createTable', 'getTable'])
->getMock();
$schemaMock->method('hasTable')->willReturn($tableExist);
$schemaMock->expects($invocationCount)->method('createTable')->willReturn($tableMock);
$schemaMock->method('getTable')->willReturn($tableMock);
$this->callMethod(
$this->sut,
'up',
[$schemaMock]
);
}
/**
* @return Generator
*/
public function canUpTableDataProvider(): Generator
{
yield 'table not exist' => [false, $this->once()];
yield 'table exist' => [true, $this->never()];
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::up
* @dataProvider canUpColumnDataProvider
*/
public function canUpColumn($columnExist, $invocationCount)
{
/** @var Column|MockObject $columnMock */
$columnMock = $this->getMockBuilder(Column::class)
->onlyMethods(['setLength'])
->disableOriginalConstructor()
->getMock();
$columnMock->method('setLength')->willReturnSelf();
/** @var Table|MockObject $tableMock */
$tableMock = $this->getMockBuilder(Table::class)
->onlyMethods(['hasColumn', 'addColumn', 'hasPrimaryKey', 'hasIndex'])
->disableOriginalConstructor()
->getMock();
$tableMock->method('hasColumn')->willReturn($columnExist);
$tableMock->expects($invocationCount)->method('addColumn')->willReturn($columnMock);
$tableMock->method('hasPrimaryKey')->willReturn(true);
$tableMock->method('hasIndex')->willReturn(true);
/** @var Schema|MockObject $schemaMock */
$schemaMock = $this->getMockBuilder(Schema::class)
->onlyMethods(['hasTable', 'getTable'])
->getMock();
$schemaMock->method('hasTable')->willReturn(true);
$schemaMock->method('getTable')->willReturn($tableMock);
$this->callMethod(
$this->sut,
'up',
[$schemaMock]
);
}
/**
* @return Generator
*/
public function canUpColumnDataProvider(): Generator
{
yield 'column not exist' => [false, $this->atLeast(7)];
yield 'column exist' => [true, $this->never()];
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::up
* @dataProvider canUpPrimaryKeyDataProvider
*/
public function canUpPrimaryKey($pKeyExist, $invocationCount)
{
/** @var Table|MockObject $tableMock */
$tableMock = $this->getMockBuilder(Table::class)
->onlyMethods(['hasColumn', 'addColumn', 'hasPrimaryKey', 'hasIndex', 'setPrimaryKey'])
->disableOriginalConstructor()
->getMock();
$tableMock->method('hasColumn')->willReturn(true);
$tableMock->method('hasPrimaryKey')->willReturn($pKeyExist);
$tableMock->method('hasIndex')->willReturn(true);
$tableMock->expects($invocationCount)->method('setPrimaryKey');
/** @var Schema|MockObject $schemaMock */
$schemaMock = $this->getMockBuilder(Schema::class)
->onlyMethods(['hasTable', 'getTable'])
->getMock();
$schemaMock->method('hasTable')->willReturn(true);
$schemaMock->method('getTable')->willReturn($tableMock);
$this->callMethod(
$this->sut,
'up',
[$schemaMock]
);
}
/**
* @return Generator
*/
public function canUpPrimaryKeyDataProvider(): Generator
{
yield 'pk not exist' => [false, $this->once()];
yield 'pk exist' => [true, $this->never()];
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::up
* @dataProvider canUpIndexDataProvider
*/
public function canUpIndex($indexExist, $invocationCount)
{
/** @var Table|MockObject $tableMock */
$tableMock = $this->getMockBuilder(Table::class)
->onlyMethods(['hasColumn', 'addColumn', 'hasPrimaryKey', 'hasIndex', 'addIndex', 'setComment'])
->disableOriginalConstructor()
->getMock();
$tableMock->method('hasColumn')->willReturn(true);
$tableMock->method('hasPrimaryKey')->willReturn(true);
$tableMock->method('hasIndex')->willReturn($indexExist);
$tableMock->expects($invocationCount)->method('addIndex');
$tableMock->expects($this->once())->method('setComment');
/** @var Schema|MockObject $schemaMock */
$schemaMock = $this->getMockBuilder(Schema::class)
->onlyMethods(['hasTable', 'getTable'])
->getMock();
$schemaMock->method('hasTable')->willReturn(true);
$schemaMock->method('getTable')->willReturn($tableMock);
$this->callMethod(
$this->sut,
'up',
[$schemaMock]
);
}
/**
* @return Generator
*/
public function canUpIndexDataProvider(): Generator
{
yield 'index not exist' => [false, $this->atLeast(2)];
yield 'index exist' => [true, $this->never()];
}
/**
* @test
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Migrations\Version20230209212939::down
* @dataProvider canDownTableDataProvider
*/
public function canDownTable($tableExist, $invocationCount)
{
/** @var Schema|MockObject $schemaMock */
$schemaMock = $this->getMockBuilder(Schema::class)
->onlyMethods(['hasTable', 'dropTable'])
->getMock();
$schemaMock->method('hasTable')->willReturn($tableExist);
$schemaMock->expects($invocationCount)->method('dropTable');
$this->callMethod(
$this->sut,
'down',
[$schemaMock]
);
}
/**
* @return Generator
*/
public function canDownTableDataProvider(): Generator
{
yield 'table exist' => [true, $this->once()];
yield 'table not exist' => [false, $this->never()];
}
}

View File

@ -1,8 +0,0 @@
# Wish list for future releases
- a more intuitive login process (instead of simply having to leave the password field blank)
- forcing the user to use Webauthn
- General avoidance of passwords, login exclusively with FIDO2
- However, a restore strategy is required in the event that a key is no longer available.
- Alternatively, a random password unknown to the customer can be set, which is changed each time the customer logs on via Webauthn.
- Implementation of resident keys for logging in completely without user input (no user name required any more)