2022-10-26 00:02:55 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace D3\Webauthn\Application\Model;
|
|
|
|
|
2022-10-31 00:11:06 +01:00
|
|
|
use Assert\AssertionFailedException;
|
2022-10-26 00:02:55 +02:00
|
|
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredential;
|
|
|
|
use D3\Webauthn\Application\Model\Credential\PublicKeyCredentialList;
|
2022-10-26 22:27:25 +02:00
|
|
|
use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn;
|
2022-10-31 00:11:06 +01:00
|
|
|
use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
|
|
|
|
use Doctrine\DBAL\Exception as DoctrineException;
|
|
|
|
use Exception;
|
2022-10-26 00:02:55 +02:00
|
|
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
|
|
|
use Nyholm\Psr7Server\ServerRequestCreator;
|
|
|
|
use OxidEsales\Eshop\Application\Model\User;
|
|
|
|
use OxidEsales\Eshop\Core\Registry;
|
2022-10-31 00:11:06 +01:00
|
|
|
use Psr\Container\ContainerExceptionInterface;
|
|
|
|
use Psr\Container\NotFoundExceptionInterface;
|
2022-10-26 00:02:55 +02:00
|
|
|
use Webauthn\PublicKeyCredentialCreationOptions;
|
|
|
|
use Webauthn\PublicKeyCredentialRequestOptions;
|
|
|
|
use Webauthn\PublicKeyCredentialSource;
|
|
|
|
use Webauthn\Server;
|
|
|
|
|
|
|
|
class Webauthn
|
|
|
|
{
|
|
|
|
public const SESSION_CREATIONS_OPTIONS = 'd3WebAuthnCreationOptions';
|
|
|
|
public const SESSION_ASSERTION_OPTIONS = 'd3WebAuthnAssertionOptions';
|
|
|
|
|
2022-10-31 00:11:06 +01:00
|
|
|
public function isAvailable(): bool
|
2022-10-26 10:15:49 +02:00
|
|
|
{
|
2022-10-28 15:02:28 +02:00
|
|
|
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' || // is HTTPS
|
|
|
|
!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ||
|
|
|
|
!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on' ||
|
|
|
|
in_array($_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1']) // is localhost
|
2022-10-26 10:15:49 +02:00
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-27 14:52:20 +02:00
|
|
|
Registry::getUtilsView()->addErrorToDisplay(
|
|
|
|
Registry::getLang()->translateString('D3_WEBAUTHN_ERR_UNSECURECONNECTION', null, true)
|
|
|
|
);
|
|
|
|
|
2022-10-26 10:15:49 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-26 00:02:55 +02:00
|
|
|
/**
|
2022-10-31 00:11:06 +01:00
|
|
|
* @param User $user
|
2022-10-26 00:02:55 +02:00
|
|
|
* @return false|string
|
2022-10-31 00:11:06 +01:00
|
|
|
* @throws ContainerExceptionInterface
|
|
|
|
* @throws DoctrineDriverException
|
|
|
|
* @throws DoctrineException
|
|
|
|
* @throws NotFoundExceptionInterface
|
2022-10-26 00:02:55 +02:00
|
|
|
*/
|
|
|
|
public function getCreationOptions(User $user)
|
|
|
|
{
|
2022-10-31 00:11:06 +01:00
|
|
|
$userEntity = oxNew(UserEntity::class, $user);
|
2022-10-26 00:02:55 +02:00
|
|
|
|
|
|
|
/** @var PublicKeyCredentialList $credentialSourceRepository */
|
|
|
|
$credentialSourceRepository = oxNew(PublicKeyCredentialList::class);
|
|
|
|
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
|
|
|
|
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) {
|
|
|
|
return $credential->getPublicKeyCredentialDescriptor();
|
|
|
|
}, $credentialSources);
|
|
|
|
|
|
|
|
$server = $this->getServer();
|
|
|
|
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
|
|
|
|
$userEntity,
|
|
|
|
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
|
|
|
$excludeCredentials
|
|
|
|
);
|
|
|
|
|
|
|
|
Registry::getSession()->setVariable(self::SESSION_CREATIONS_OPTIONS, $publicKeyCredentialCreationOptions);
|
|
|
|
|
|
|
|
return json_encode($publicKeyCredentialCreationOptions,JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
|
|
}
|
|
|
|
|
2022-10-31 00:11:06 +01:00
|
|
|
/**
|
|
|
|
* @return false|string
|
|
|
|
* @throws DoctrineDriverException
|
|
|
|
* @throws DoctrineException
|
|
|
|
* @throws ContainerExceptionInterface
|
|
|
|
* @throws NotFoundExceptionInterface
|
|
|
|
*/
|
|
|
|
public function getRequestOptions(string $userId)
|
2022-10-26 00:02:55 +02:00
|
|
|
{
|
2022-10-27 14:52:49 +02:00
|
|
|
/** @var d3_User_Webauthn $user */
|
2022-10-26 00:02:55 +02:00
|
|
|
$user = oxNew(User::class);
|
2022-10-31 00:11:06 +01:00
|
|
|
$user->load($userId);
|
|
|
|
$userEntity = oxNew(UserEntity::class, $user);
|
2022-10-26 00:02:55 +02:00
|
|
|
|
|
|
|
// Get the list of authenticators associated to the user
|
2022-10-28 00:45:32 +02:00
|
|
|
$credentialList = oxNew(PublicKeyCredentialList::class);
|
|
|
|
$credentialSources = $credentialList->findAllForUserEntity($userEntity);
|
2022-10-26 00:02:55 +02:00
|
|
|
|
|
|
|
// Convert the Credential Sources into Public Key Credential Descriptors
|
|
|
|
$allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) {
|
|
|
|
return $credential->getPublicKeyCredentialDescriptor();
|
|
|
|
}, $credentialSources);
|
|
|
|
|
|
|
|
$server = $this->getServer();
|
|
|
|
|
|
|
|
// We generate the set of options.
|
|
|
|
$publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
|
|
|
|
PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value
|
|
|
|
$allowedCredentials
|
|
|
|
);
|
|
|
|
|
|
|
|
Registry::getSession()->setVariable(self::SESSION_ASSERTION_OPTIONS, $publicKeyCredentialRequestOptions);
|
|
|
|
|
|
|
|
return json_encode($publicKeyCredentialRequestOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Server
|
|
|
|
*/
|
2022-10-31 00:11:06 +01:00
|
|
|
public function getServer(): Server
|
2022-10-26 00:02:55 +02:00
|
|
|
{
|
2022-10-31 00:11:06 +01:00
|
|
|
$rpEntity = oxNew(RelyingPartyEntity::class);
|
|
|
|
return oxNew(Server::class, $rpEntity, oxNew(PublicKeyCredentialList::class));
|
2022-10-26 00:02:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function saveAuthn(string $credential, string $keyName = null)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$psr17Factory = new Psr17Factory();
|
|
|
|
$creator = new ServerRequestCreator(
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory
|
|
|
|
);
|
|
|
|
$serverRequest = $creator->fromGlobals();
|
|
|
|
|
|
|
|
$publicKeyCredentialSource = $this->getServer()->loadAndCheckAttestationResponse(
|
|
|
|
html_entity_decode($credential),
|
|
|
|
Registry::getSession()->getVariable(self::SESSION_CREATIONS_OPTIONS),
|
|
|
|
$serverRequest
|
|
|
|
);
|
|
|
|
|
|
|
|
$pkCredential = oxNew(PublicKeyCredential::class);
|
|
|
|
$pkCredential->saveCredentialSource($publicKeyCredentialSource, $keyName);
|
2022-10-31 00:11:06 +01:00
|
|
|
} catch (Exception $e) {
|
|
|
|
// ToDo: write exc msg to display and log
|
2022-10-26 00:02:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-31 00:11:06 +01:00
|
|
|
/**
|
|
|
|
* @param string $response
|
|
|
|
* @return bool
|
|
|
|
* @throws AssertionFailedException
|
|
|
|
* @throws WebauthnException
|
|
|
|
*/
|
|
|
|
public function assertAuthn(string $response): bool
|
2022-10-26 00:02:55 +02:00
|
|
|
{
|
2022-10-28 00:45:32 +02:00
|
|
|
$psr17Factory = new Psr17Factory();
|
|
|
|
$creator = new ServerRequestCreator(
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory,
|
|
|
|
$psr17Factory
|
|
|
|
);
|
|
|
|
$serverRequest = $creator->fromGlobals();
|
2022-10-26 00:02:55 +02:00
|
|
|
|
2022-10-28 00:45:32 +02:00
|
|
|
$user = oxNew(User::class);
|
2022-10-31 00:11:06 +01:00
|
|
|
$user->load(Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER));
|
|
|
|
$userEntity = oxNew(UserEntity::class, $user);
|
2022-10-26 00:02:55 +02:00
|
|
|
|
2022-10-28 00:45:32 +02:00
|
|
|
$this->getServer()->loadAndCheckAssertionResponse(
|
|
|
|
html_entity_decode($response),
|
|
|
|
Registry::getSession()->getVariable(self::SESSION_ASSERTION_OPTIONS),
|
|
|
|
$userEntity,
|
|
|
|
$serverRequest
|
|
|
|
);
|
2022-10-30 00:27:11 +02:00
|
|
|
|
|
|
|
return true;
|
2022-10-26 00:02:55 +02:00
|
|
|
}
|
2022-10-26 22:27:25 +02:00
|
|
|
|
|
|
|
/**
|
2022-10-28 00:45:32 +02:00
|
|
|
* @param $userId
|
2022-10-26 22:27:25 +02:00
|
|
|
* @return bool
|
2022-10-31 00:11:06 +01:00
|
|
|
* @throws ContainerExceptionInterface
|
|
|
|
* @throws DoctrineDriverException
|
|
|
|
* @throws DoctrineException
|
|
|
|
* @throws NotFoundExceptionInterface
|
2022-10-26 22:27:25 +02:00
|
|
|
*/
|
|
|
|
public function isActive($userId): bool
|
|
|
|
{
|
2022-10-31 23:17:04 +01:00
|
|
|
return !Registry::getConfig()->getConfigParam(WebauthnConf::GLOBAL_SWITCH)
|
|
|
|
&& !Registry::getSession()->getVariable(WebauthnConf::GLOBAL_SWITCH)
|
2022-10-26 22:27:25 +02:00
|
|
|
&& $this->UserUseWebauthn($userId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $userId
|
|
|
|
* @return bool
|
2022-10-31 00:11:06 +01:00
|
|
|
* @throws ContainerExceptionInterface
|
|
|
|
* @throws DoctrineDriverException
|
|
|
|
* @throws DoctrineException
|
|
|
|
* @throws NotFoundExceptionInterface
|
2022-10-26 22:27:25 +02:00
|
|
|
*/
|
|
|
|
public function UserUseWebauthn($userId): bool
|
|
|
|
{
|
|
|
|
$user = oxNew(User::class);
|
|
|
|
$user->load($userId);
|
2022-10-31 00:11:06 +01:00
|
|
|
$entity = oxNew(UserEntity::class, $user);
|
|
|
|
|
|
|
|
$credentialList = oxNew(PublicKeyCredentialList::class);
|
|
|
|
$list = $credentialList->findAllForUserEntity($entity);
|
2022-10-26 22:27:25 +02:00
|
|
|
|
|
|
|
return is_array($list) && count($list);
|
|
|
|
}
|
2022-10-26 00:02:55 +02:00
|
|
|
}
|