webauthn/src/Application/Model/Webauthn.php

212 lines
7.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace D3\Webauthn\Application\Model;
2022-10-31 00:11:06 +01:00
use Assert\AssertionFailedException;
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;
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;
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
{
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
) {
return true;
}
Registry::getUtilsView()->addErrorToDisplay(
Registry::getLang()->translateString('D3_WEBAUTHN_ERR_UNSECURECONNECTION', null, true)
);
return false;
}
/**
2022-10-31 00:11:06 +01:00
* @param User $user
* @return false|string
2022-10-31 00:11:06 +01:00
* @throws ContainerExceptionInterface
* @throws DoctrineDriverException
* @throws DoctrineException
* @throws NotFoundExceptionInterface
*/
public function getCreationOptions(User $user)
{
2022-10-31 00:11:06 +01:00
$userEntity = oxNew(UserEntity::class, $user);
/** @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-27 14:52:49 +02:00
/** @var d3_User_Webauthn $user */
$user = oxNew(User::class);
2022-10-31 00:11:06 +01:00
$user->load($userId);
$userEntity = oxNew(UserEntity::class, $user);
// Get the list of authenticators associated to the user
$credentialList = oxNew(PublicKeyCredentialList::class);
$credentialSources = $credentialList->findAllForUserEntity($userEntity);
// 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-31 00:11:06 +01:00
$rpEntity = oxNew(RelyingPartyEntity::class);
return oxNew(Server::class, $rpEntity, oxNew(PublicKeyCredentialList::class));
}
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-31 00:11:06 +01:00
/**
* @param string $response
* @return bool
* @throws AssertionFailedException
* @throws WebauthnException
*/
public function assertAuthn(string $response): bool
{
$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator(
$psr17Factory,
$psr17Factory,
$psr17Factory,
$psr17Factory
);
$serverRequest = $creator->fromGlobals();
$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);
$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 22:27:25 +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
{
return false == Registry::getConfig()->getConfigParam('blDisableWebauthnGlobally')
&& $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);
}
}