8
0
Fork 0

Commits vergleichen

...

16 Commits

Autor SHA1 Nachricht Datum
Daniel Seifert 0b7958ddb9
add product logo 2023-06-09 09:32:15 +02:00
Daniel Seifert e4e27c8952
adjust version informations 2023-05-26 09:37:10 +02:00
Daniel Seifert 20bb53c215
make installable in OXID 6.5.2 2023-05-26 09:33:54 +02:00
Daniel Seifert d61dcda7fb
adjust version informations 2023-02-18 22:07:52 +01:00
Daniel Seifert 08e58b1b88
define compatible TOTP plugin version 2023-02-18 22:05:52 +01:00
Daniel Seifert dec4b3cebd
rename from security key to login key 2023-02-17 09:21:52 +01:00
Daniel Seifert c63724c064
improve assertions 2023-02-17 09:21:51 +01:00
Daniel Seifert 018e91bc0c
add any further debug loggings 2023-02-16 10:31:53 +01:00
Daniel Seifert 10d8fddd88
add wishlist 2023-02-16 10:31:52 +01:00
Daniel Seifert cc0e0fb32b
update changelog 2023-02-15 22:33:56 +01:00
Daniel Seifert 291c99e4e5
convert the user handle to an arrayBuffer
because of format errors in case of given handle (usually concerns platform authenticators only)
2023-02-14 16:27:29 +01:00
Daniel Seifert 462bb34447
add migrations 2023-02-11 23:20:55 +01:00
Daniel Seifert 028fc05d54
add missing integration test 2023-02-06 22:39:23 +01:00
Daniel Seifert e11b93e300
assert some expectations 2023-02-06 22:38:03 +01:00
Daniel Seifert e72f365a29
adjust tests 2023-02-05 23:45:16 +01:00
Daniel Seifert 161787d26f
assert valid credential response 2023-02-05 22:50:19 +01:00
39 geänderte Dateien mit 909 neuen und 371 gelöschten Zeilen

Datei anzeigen

@ -4,13 +4,14 @@ 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/2.0.0.1...rel_2.x)
## [Unreleased](https://git.d3data.de/D3Public/webauthn/compare/1.0.0.0...rel_1.x)
## [1.0.0.0](https://git.d3data.de/D3Public/webauthn/releases/tag/1.0.0.0) - 2019-08-19
## [1.0.0.0](https://git.d3data.de/D3Public/webauthn/compare/0.1.0.0...1.0.0.0) - 2023-05-25
### Added
- 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
- 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)

Datei anzeigen

@ -1,13 +1,15 @@
[![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
With this module, the login in the OXID shop can be carried out with a hardware token instead of a password (WebAuthn / FIDO2 based).
![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.
This secures the login in the frontend and (if allowed for the user) also in the backend.
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)".
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)".
The key management is done in the admin area and in the user's "My Account".
@ -63,12 +65,14 @@ and its requirements.
The Flow and Wave themes are supported by default. Other themes may require customisation.
## Module installation
## Module installation / update
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.
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.
```bash
php composer require d3/oxid-twofactor-passwordless:^1.0
composer require d3/oxid-twofactor-passwordless:^1.0
./vendor/bin/oe-eshop-db_migrate migrations:migrate d3webauthn
```
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 ...`).

Datei anzeigen

@ -1,13 +1,15 @@
[![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
Mit diesem Modul kann die Anmeldung im OXID-Shop mit einem Hardwaretoken anstelle eines Passworts durchgeführt werden (WebAuthn / FIDO2 basiert).
![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.
Hierbei wird die Anmeldung im Frontend und (sofern für den Benutzer erlaubt) auch im Backend gesichert.
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.
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.
Die Schlüsselverwaltung erfolgt im Adminbereich sowie im "Mein Konto" des Benutzers.
@ -63,12 +65,14 @@ und dessen Anforderungen.
Im Standard wird das Flow- und Wave-Theme unterstützt. Andere Themes können Anpassungen erfordern.
## Modulinstallation
## Modulinstallation / -update
Ö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.
Ö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.
```bash
php composer require d3/oxid-twofactor-passwordless:^1.0
composer require d3/oxid-twofactor-passwordless:^1.0
./vendor/bin/oe-eshop-db_migrate migrations:migrate d3webauthn
```
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 ...`).

Datei anzeigen

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

Datei anzeigen

@ -15,7 +15,9 @@ 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;
@ -83,7 +85,7 @@ class d3user_webauthn extends AdminDetailsController
try {
$this->setPageType('requestnew');
$this->setAuthnRegister();
} catch (Exception|ContainerExceptionInterface|NotFoundExceptionInterface|DoctrineDriverException $e) {
} catch (AssertionFailedException|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());
@ -107,12 +109,15 @@ class d3user_webauthn extends AdminDetailsController
}
$credential = Registry::getRequest()->getRequestEscapedParameter('credential');
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'));
}
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);
} catch (WebauthnException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error($e->getDetailedErrorMessage(), ['UserId' => $this->getEditObjectId()]);
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug($e->getTraceAsString());
@ -136,11 +141,14 @@ 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);
@ -165,6 +173,7 @@ class d3user_webauthn extends AdminDetailsController
*/
public function getCredentialList($userId): array
{
/** @var User $oUser */
$oUser = d3GetOxidDIC()->get('d3ox.webauthn.'.User::class);
$oUser->load($userId);

Datei anzeigen

@ -15,6 +15,9 @@ 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;
@ -26,7 +29,6 @@ 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;
@ -60,11 +62,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');
}
@ -107,11 +109,18 @@ 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');
}
}
@ -122,10 +131,10 @@ class d3webauthnadminlogin extends AdminController
{
try {
$login = $this->getWebAuthnLogin();
return $login->adminLogin(
d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('profile')
);
} catch (WebauthnGetException $e) {
$profile = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('profile');
Assert::that($profile)->string();
return $login->adminLogin($profile);
} catch (WebauthnGetException|AssertionFailedException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.UtilsView::class)->addErrorToDisplay($e);
return 'login';
}
@ -165,16 +174,19 @@ class d3webauthnadminlogin extends AdminController
/**
* @return WebauthnLogin
* @throws InvalidArgumentException
*/
protected function getWebAuthnLogin(): WebauthnLogin
{
/** @var Request $request */
$request = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class);
return oxNew(
WebauthnLogin::class,
$request->getRequestEscapedParameter('credential'),
$request->getRequestEscapedParameter('error')
);
$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);
}
}

Datei anzeigen

@ -15,7 +15,9 @@ 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;
@ -86,6 +88,10 @@ 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);
}
}
@ -103,6 +109,7 @@ class d3_account_webauthn extends AccountController
* @throws DoctrineException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
* @throws InvalidArgumentException
* @return void
*/
public function setAuthnRegister(): void
@ -137,11 +144,10 @@ class d3_account_webauthn extends AccountController
}
$credential = d3GetOxidDIC()->get('d3ox.webauthn.'.Request::class)->getRequestEscapedParameter('credential');
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'));
}
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'));
} catch (WebauthnException $e) {
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->error(
$e->getDetailedErrorMessage(),

Datei anzeigen

@ -15,6 +15,7 @@ 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;
@ -62,9 +63,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');
}
@ -72,7 +73,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']);
}
@ -103,6 +104,13 @@ 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');
}
}

Datei anzeigen

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

Datei anzeigen

@ -15,7 +15,9 @@ 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;
@ -24,7 +26,6 @@ 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;
@ -76,6 +77,7 @@ class Webauthn
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws NotFoundExceptionInterface
* @throws InvalidArgumentException
*/
public function getCreationOptions(User $user): string
{
@ -94,9 +96,7 @@ class Webauthn
$json = $this->jsonEncode($publicKeyCredentialCreationOptions);
if ($json === false) {
throw oxNew(Exception::class, "can't encode creation options");
}
Assert::that($json)->isJsonString("can't encode request options");
return $json;
}
@ -134,6 +134,7 @@ class Webauthn
* @return string
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws InvalidArgumentException
*/
public function getRequestOptions(string $userId): string
{
@ -143,11 +144,16 @@ 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
$this->getExistingCredentials($userEntity)
$existingCredentials
);
d3GetOxidDIC()->get('d3ox.webauthn.'.Session::class)
@ -155,9 +161,11 @@ class Webauthn
$json = $this->jsonEncode($publicKeyCredentialRequestOptions);
if ($json === false) {
throw oxNew(Exception::class, "can't encode request options");
}
d3GetOxidDIC()->get('d3ox.webauthn.'.LoggerInterface::class)->debug(
'request options: '.$json
);
Assert::that($json)->isJsonString("can't encode request options");
return $json;
}

Datei anzeigen

@ -17,6 +17,7 @@ namespace D3\Webauthn\Application\Model;
use D3\TestingTools\Production\IsMockable;
use OxidEsales\Eshop\Core\Language;
use Psr\Log\LoggerInterface;
class WebauthnErrors
{
@ -38,6 +39,10 @@ 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;

Datei anzeigen

@ -110,10 +110,11 @@ 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();
@ -158,10 +159,11 @@ 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();
@ -195,8 +197,6 @@ 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));

Datei anzeigen

@ -20,15 +20,15 @@ $aLang = [
'charset' => 'UTF-8',
'PAGE_TITLE_D3WEBAUTHNLOGIN' => 'Passwortloses Anmelden',
'D3_WEBAUTHN_ACCOUNT' => 'Meine Schlüssel',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'Meine Schlüssel',
'D3_WEBAUTHN_ACCOUNT' => 'Meine Anmeldeschlüssel',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'Meine Anmeldeschlüssel',
'D3_WEBAUTHN_ACCOUNT_DESC' => 'Verwalten Sie hier Ihre Anmeldeschlüssel.',
'D3_WEBAUTHN_ACC_REGISTERNEW' => 'neue Registrierung erstellen',
'D3_WEBAUTHN_ACC_ADDKEY' => 'Sicherheitsschlüssel hinzufügen',
'D3_WEBAUTHN_ACC_ADDKEY' => 'Anmeldeschlüssel hinzufügen',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registrierte Schlüssel',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registrierte Anmeldeschlüssel',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Hardwareschlüssel authentisieren.',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Anmeldeschlü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 Schlüssel registriert',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'kein Anmeldeschlü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 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_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_NOTLOADEDUSER' => "Kann keine Anmeldedaten von nicht geladenem Kundenkonto beziehen.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "Der Schlüssel kann aus technischen Gründen nicht registriert werden. Bitte wenden Sie sich an den Shopbetreiber.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "Der Anmeldeschlüssel kann aus technischen Gründen nicht registriert werden. Bitte wenden Sie sich an den Shopbetreiber.",
];

Datei anzeigen

@ -20,15 +20,15 @@ $aLang = [
'charset' => 'UTF-8',
'PAGE_TITLE_D3WEBAUTHNLOGIN' => 'Passwordless login',
'D3_WEBAUTHN_ACCOUNT' => 'My keys',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'My keys',
'D3_WEBAUTHN_ACCOUNT' => 'My login keys',
'PAGE_TITLE_D3_ACCOUNT_WEBAUTHN' => 'My login keys',
'D3_WEBAUTHN_ACCOUNT_DESC' => 'Manage your login keys here.',
'D3_WEBAUTHN_ACC_REGISTERNEW' => 'create new registration',
'D3_WEBAUTHN_ACC_ADDKEY' => 'add security key',
'D3_WEBAUTHN_ACC_ADDKEY' => 'add login key',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registered keys',
'D3_WEBAUTHN_ACC_REGISTEREDKEYS' => 'registered login keys',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with hardware key.',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with login 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 key registered',
'D3_WEBAUTHN_NOKEYREGISTERED' => 'no login 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 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_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_NOTLOADEDUSER' => "Cannot obtain login data from unloaded customer account.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "The key cannot be registered for technical reasons. Please contact the shop operator.",
'D3_WEBAUTHN_ERR_NOTCREDENTIALNOTSAVEABLE' => "The login key cannot be registered for technical reasons. Please contact the shop operator.",
];

Datei anzeigen

@ -18,35 +18,35 @@ $sLangName = "Deutsch";
$aLang = [
'charset' => 'UTF-8',
'D3_WEBAUTHN_ERROR_UNVALID' => 'Der verwendete Schlüssel ist ungültig oder kann nicht geprüft werden.',
'D3_WEBAUTHN_ERROR_UNVALID' => 'Der verwendete Anmeldeschlü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 Hardwareschlüssel authentisieren.',
'WEBAUTHN_INPUT_HELP' => 'Bitte mit Anmeldeschlü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 Schlüssel wirklich gelöscht werden?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'kein Schlüssel registriert',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Soll der Anmeldeschlüssel wirklich gelöscht werden?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'kein Anmeldeschlüssel registriert',
'd3mxuser_webauthn' => 'Hardwareschlüssel',
'd3mxuser_webauthn' => 'Anmeldeschlüssel',
'D3_WEBAUTHN_REGISTERNEW' => 'neue Registrierung erstellen',
'D3_WEBAUTHN_ADDKEY' => 'Sicherheitsschlüssel hinzufügen',
'D3_WEBAUTHN_ADDKEY' => 'Anmeldeschlüssel hinzufügen',
'D3_WEBAUTHN_KEYNAME' => 'Name des Schlüssels',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registrierte Schlüssel',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registrierte Anmeldeschlüssel',
'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_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_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 Hardwareschlüsseln leider nicht.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Ihr Browser unterstützt die Verwendung von Anmeldeschlü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 Sicherheitsschlü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 Anmeldeschlüssel ist aus technischen Gründen derzeit leider nicht möglich. Bitte verwenden Sie statt dessen Ihr Passwort.',
];

Datei anzeigen

@ -18,35 +18,35 @@ $sLangName = "English";
$aLang = [
'charset' => 'UTF-8',
'D3_WEBAUTHN_ERROR_UNVALID' => 'The key used is invalid or cannot be checked.',
'D3_WEBAUTHN_ERROR_UNVALID' => 'The used login key 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 hardware key.',
'WEBAUTHN_INPUT_HELP' => 'Please authenticate with login 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 key?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'No key registered',
'D3WEBAUTHN_DELETE_CONFIRM' => 'Do you really want to delete the login key?',
'D3WEBAUTHN_CANCELNOKEYREGISTERED' => 'No login key registered',
'd3mxuser_webauthn' => 'hardware key',
'd3mxuser_webauthn' => 'login keys',
'D3_WEBAUTHN_REGISTERNEW' => 'create new registration',
'D3_WEBAUTHN_ADDKEY' => 'add security key',
'D3_WEBAUTHN_ADDKEY' => 'add login key',
'D3_WEBAUTHN_KEYNAME' => 'Key name',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered keys',
'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered login keys',
'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_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_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 hardware keys.',
'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Unfortunately, your browser does not support the use of login 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 security key is currently not possible for technical reasons. Please use your password instead.',
'D3_WEBAUTHN_ERR_LOGINPROHIBITED' => 'Unfortunately, logging in with a login key is currently not possible for technical reasons. Please use your password instead.',
];

Datei anzeigen

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

Datei anzeigen

@ -17,6 +17,7 @@ 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;
@ -65,29 +66,27 @@ 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)) {
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()
);
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()
);
$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);
}
}
@ -163,6 +162,7 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent
/**
* @return WebauthnLogin
* @throws InvalidArgumentException
*/
protected function d3GetWebauthnLogin(): WebauthnLogin
{
@ -172,8 +172,7 @@ 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')
->notEmpty('credential value expected contained content');
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);

Datei anzeigen

@ -18,12 +18,10 @@ 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;
@ -39,97 +37,25 @@ use Psr\Log\LoggerInterface;
class Actions
{
use IsMockable;
public const FIELDLENGTH_CREDID = 512;
public const FIELDLENGTH_CREDENTIAL = 2000;
public $seo_de = 'sicherheitsschluessel';
public $seo_en = 'en/key-authentication';
public $seo_de = 'anmeldeschluessel';
public $seo_en = 'en/login-keys';
public $stdClassName = 'd3_account_webauthn';
/**
* SQL statement, that will be executed only at the first time of module installation.
*
* @var string
* @throws Exception
*/
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()
public function runModuleMigrations()
{
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);
/** @var MigrationsBuilder $migrationsBuilder */
$migrationsBuilder = d3GetOxidDIC()->get('d3ox.webauthn.'.MigrationsBuilder::class);
$migrations = $migrationsBuilder->build();
$migrations->execute('migrations:migrate', 'd3webauthn');
}
/**
* Regenerate views for changed tables
* @throws Exception
*/
public function regenerateViews()
{
@ -139,6 +65,7 @@ class Actions
/**
* clear cache
* @throws Exception
*/
public function clearCache()
{
@ -204,6 +131,7 @@ class Actions
/**
* @return void
* @throws Exception
*/
public function seoUrl()
{
@ -220,9 +148,11 @@ 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() .
@ -234,6 +164,7 @@ class Actions
/**
* @return void
* @throws Exception
*/
public function createSeoUrl()
{

Datei anzeigen

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

BIN
src/logo.png Normale Datei

Binäre Datei nicht angezeigt.

Nachher

Breite:  |  Höhe:  |  Größe: 4.4 KiB

Datei anzeigen

@ -66,14 +66,15 @@ $logo = '<img src="https://logos.oxidmodule.com/d3logo.svg" alt="(D3)" style="he
$aModule = [
'id' => $sModuleId,
'title' => [
'de' => $logo.' zweiter Faktor - Passwortlose Anmeldung',
'en' => $logo.' second factor - passwordless login',
'de' => $logo.' zweiter Faktor - Passwortlose Anmeldung mit passkeys',
'en' => $logo.' second factor - passwordless login with passkeys',
],
'description' => [
'de' => 'Passwortlose Anmeldung f&uuml;r OXID eSales Shop (WebAuthn / FIDO2 basiert)',
'en' => 'Passwordless login for OXID eSales shop (WebAuthn / FIDO2 based)',
'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)',
],
'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/',

Datei anzeigen

@ -0,0 +1,112 @@
<?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');
}
}
}

4
src/migration/migrations.yml Ausführbare Datei
Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,14 @@
<?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;

Datei anzeigen

@ -17,15 +17,15 @@
namespace D3\Webauthn\tests\integration;
use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler;
use D3\ModCfg\Tests\unit\d3ModCfgUnitTestCase;
use D3\DIContainerHandler\d3DicHandler;
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 d3ModCfgUnitTestCase
abstract class integrationTestCase extends UnitTestCase
{
/**
* Set up fixture.

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,167 @@
<?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'],
];
}
}

Datei anzeigen

@ -15,6 +15,7 @@
<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>

Datei anzeigen

@ -13,6 +13,7 @@
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;
@ -172,7 +173,7 @@ class d3user_webauthnTest extends WAUnitTestCase
])
->getMock();
$sutMock->expects($this->atLeastOnce())->method('setPageType');
$sutMock->expects($this->atLeastOnce())->method('setAuthnRegister')->willThrowException(oxNew(WebauthnException::class));
$sutMock->expects($this->atLeastOnce())->method('setAuthnRegister')->willThrowException(new InvalidArgumentException('msg', 20));
$this->callMethod(
$sutMock,

Datei anzeigen

@ -144,11 +144,15 @@ 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($redirectClass = 'login', $userVarName = WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER)
{
parent::generateCredentialRequestFailed($redirectClass, $userVarName);
public function generateCredentialRequestFailed(
$exception,
$redirectClass = 'login',
$userVarName = WebauthnConf::WEBAUTHN_ADMIN_SESSION_CURRENTUSER
) {
parent::generateCredentialRequestFailed($exception, $redirectClass, $userVarName);
}
/**

Datei anzeigen

@ -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->expects($this->never())->method('debug')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var d3_account_webauthn|MockObject $oControllerMock */
@ -210,9 +210,10 @@ 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()
public function canRequestNewCredentialCantGetCreationOptions($exception)
{
$oUser = oxNew(User::class);
$oUser->setId('foo');
@ -225,7 +226,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->expects($this->atLeastOnce())->method('debug')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var d3_account_webauthn|MockObject $oControllerMock */
@ -233,7 +234,7 @@ class d3_account_webauthnTest extends WAUnitTestCase
->onlyMethods(['setAuthnRegister', 'setPageType', 'getUser'])
->getMock();
$oControllerMock->expects($this->atLeastOnce())->method('setAuthnRegister')
->willThrowException(oxNew(WebauthnException::class));
->willThrowException($exception);
$oControllerMock->expects($this->never())->method('setPageType');
$oControllerMock->method('getUser')->willReturn($oUser);
@ -245,6 +246,16 @@ 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
@ -334,7 +345,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->expects($this->once())->method('debug')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var User|MockObject $userMock */
@ -425,7 +436,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->expects($this->once())->method('debug')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var User|MockObject $userMock */

Datei anzeigen

@ -15,6 +15,7 @@ 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;
@ -144,7 +145,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->expects($this->never())->method('debug')->willReturn(true);
$loggerMock->method('debug')->willReturn(true);
d3GetOxidDIC()->set('d3ox.webauthn.'.LoggerInterface::class, $loggerMock);
/** @var Session|MockObject $sessionMock */
@ -181,9 +182,14 @@ class d3webauthnloginTest extends WAUnitTestCase
* @test
* @return void
* @throws ReflectionException
* @dataProvider generateCredentialRequestFailedDataProvider
* @covers \D3\Webauthn\Application\Controller\d3webauthnlogin::generateCredentialRequest
*/
public function generateCredentialRequestFailed($redirectClass = 'start', $userVarName = WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER)
public function generateCredentialRequestFailed(
$exception,
$redirectClass = 'start',
$userVarName = WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER
)
{
$currUserFixture = 'currentUserFixture';
@ -209,7 +215,7 @@ class d3webauthnloginTest extends WAUnitTestCase
->onlyMethods(['getRequestOptions'])
->getMock();
$webAuthnMock->expects($this->once())->method('getRequestOptions')->with($currUserFixture)
->willThrowException(oxNew(WebauthnException::class, 'foobar0'));
->willThrowException($exception);
d3GetOxidDIC()->set(Webauthn::class, $webAuthnMock);
/** @var Utils|MockObject $utilsMock */
@ -233,6 +239,15 @@ 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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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,141 +45,35 @@ class ActionsTest extends WAUnitTestCase
/**
* @test
* @param $tableExist
* @param $expectedInvocation
* @return void
* @throws ReflectionException
* @covers \D3\Webauthn\Setup\Actions::setupModule
* @dataProvider canSetupModuleDataProvider
* @covers \D3\Webauthn\Setup\Actions::runModuleMigrations()
*/
public function canSetupModule($tableExist, $expectedInvocation)
public function canRunModuleMigrations()
{
/** @var Actions|MockObject $sut */
$sut = $this->getMockBuilder(Actions::class)
->onlyMethods(['tableExists', 'executeSQL'])
->getMock();
$sut->method('tableExists')->willReturn($tableExist);
$sut->expects($expectedInvocation)->method('executeSQL')->willReturn(true);
$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();
$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)
/** @var Migrations|MockObject $migrationMock */
$migrationMock = $this->getMockBuilder(Migrations::class)
->onlyMethods(['execute'])
->disableOriginalConstructor()
->getMock();
$dbMock->expects($this->once())->method('execute');
$sut = $this->getMockBuilder(Actions::class)
->onlyMethods(['d3GetDb'])
->getMock();
$sut->method('d3GetDb')->willReturn($dbMock);
$this->callMethod(
$sut,
'executeSQL',
['query']
$migrationMock->expects($this->atLeastOnce())->method('execute')->with(
$this->identicalTo('migrations:migrate'),
$this->identicalTo('d3webauthn')
);
}
/**
* @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'])
/** @var MigrationsBuilder|MockObject $migrationsBuilderMock */
$migrationsBuilderMock = $this->getMockBuilder(MigrationsBuilder::class)
->onlyMethods(['build'])
->getMock();
$DbMetaDataMock->expects($this->once())->method('fieldExists')->willReturn($expected);
d3GetOxidDIC()->set('d3ox.webauthn.'.DbMetaDataHandler::class, $DbMetaDataMock);
$migrationsBuilderMock->method('build')->willReturn($migrationMock);
d3GetOxidDIC()->set('d3ox.webauthn.'.MigrationsBuilder::class, $migrationsBuilderMock);
/** @var Actions $sut */
$sut = oxNew(Actions::class);
$this->assertSame(
$expected,
$this->callMethod(
$sut,
'fieldExists',
['testField', 'testTable']
)
$this->callMethod(
$sut,
'runModuleMigrations'
);
}

Datei anzeigen

@ -0,0 +1,303 @@
<?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()];
}
}

8
wishlist.md Normale Datei
Datei anzeigen

@ -0,0 +1,8 @@
# 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)