Init Gitea
This commit is contained in:
409
Service/CustomerPasskeysAuthService.php
Normal file
409
Service/CustomerPasskeysAuthService.php
Normal file
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of EC-CUBE
|
||||
*
|
||||
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
|
||||
*
|
||||
* http://www.ec-cube.co.jp/
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Plugin\Passkeys\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Eccube\Common\EccubeConfig;
|
||||
use Eccube\Entity\BaseInfo;
|
||||
use Eccube\Entity\Customer;
|
||||
use Eccube\Repository\BaseInfoRepository;
|
||||
use Plugin\Passkeys\Entity\PasskeysCustomerCookie;
|
||||
use Plugin\Passkeys\Repository\PasskeysAuthConfigRepository;
|
||||
use Plugin\Passkeys\Repository\PasskeysAuthCustomerCookieRepository;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Twilio\Exceptions\ConfigurationException;
|
||||
use Twilio\Rest\Client;
|
||||
|
||||
class CustomerPasskeysAuthService
|
||||
{
|
||||
/**
|
||||
* @var string コールバックURL
|
||||
*/
|
||||
public const SESSION_CALL_BACK_URL = 'plugin_eccube_customer_passkeys_call_back_url';
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
/**
|
||||
* @var EccubeConfig
|
||||
*/
|
||||
protected $eccubeConfig;
|
||||
/**
|
||||
* @var EncoderFactoryInterface
|
||||
*/
|
||||
protected $encoderFactory;
|
||||
/**
|
||||
* @var RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cookieName;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $routeCookieName;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $expire;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $route_expire;
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $entityManager;
|
||||
/**
|
||||
* @var BaseInfo|object|null
|
||||
*/
|
||||
private $baseInfo;
|
||||
|
||||
/**
|
||||
* @var PasskeysAuthConfig
|
||||
*/
|
||||
private $PasskeysAuthConfig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $default_tfa_routes = [
|
||||
'login',
|
||||
'mypage_login',
|
||||
'mypage',
|
||||
'mypage_order',
|
||||
'mypage_favorite',
|
||||
'mypage_change',
|
||||
'mypage_delivery',
|
||||
'mypage_withdraw',
|
||||
'shopping',
|
||||
'shopping_login',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var PasskeysAuthCustomerCookieRepository
|
||||
*/
|
||||
private PasskeysAuthCustomerCookieRepository $passkeysCustomerCookieRepository;
|
||||
|
||||
/**
|
||||
* @var PasswordHasherFactoryInterface
|
||||
*/
|
||||
private PasswordHasherFactoryInterface $hashFactory;
|
||||
|
||||
/**
|
||||
* constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $entityManager
|
||||
* @param EccubeConfig $eccubeConfig
|
||||
* @param BaseInfoRepository $baseInfoRepository
|
||||
* @param PasskeysAuthConfigRepository $PasskeysAuthConfigRepository
|
||||
* @param PasskeysAuthCustomerCookieRepository $passkeysCustomerCookieRepository
|
||||
* @param PasswordHasherFactoryInterface $hashFactory
|
||||
*/
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
EccubeConfig $eccubeConfig,
|
||||
BaseInfoRepository $baseInfoRepository,
|
||||
RequestStack $requestStack,
|
||||
PasskeysAuthConfigRepository $PasskeysAuthConfigRepository,
|
||||
PasskeysAuthCustomerCookieRepository $passkeysCustomerCookieRepository,
|
||||
PasswordHasherFactoryInterface $hashFactory
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->eccubeConfig = $eccubeConfig;
|
||||
|
||||
$this->baseInfo = $baseInfoRepository->find(1);
|
||||
$this->request = $requestStack->getCurrentRequest();
|
||||
$this->cookieName = $this->eccubeConfig->get('plugin_eccube_passkeys_customer_cookie_name');
|
||||
$this->routeCookieName = $this->eccubeConfig->get('plugin_eccube_passkeys_route_customer_cookie_name');
|
||||
|
||||
$this->expire = (int) $this->eccubeConfig->get('plugin_eccube_passkeys_customer_expire');
|
||||
$this->route_expire = (int) $this->eccubeConfig->get('plugin_eccube_passkeys_route_customer_expire');
|
||||
|
||||
$this->PasskeysAuthConfig = $PasskeysAuthConfigRepository->findOne();
|
||||
$this->passkeysCustomerCookieRepository = $passkeysCustomerCookieRepository;
|
||||
$this->hashFactory = $hashFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultAuthRoutes()
|
||||
{
|
||||
return $this->default_tfa_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container): ?ContainerInterface
|
||||
{
|
||||
$previous = $this->container;
|
||||
$this->container = $container;
|
||||
|
||||
return $previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2段階認証用Cookie生成.
|
||||
*
|
||||
* @param Customer $Customer
|
||||
* @param null $route
|
||||
*
|
||||
* @return Cookie
|
||||
*/
|
||||
public function createAuthedCookie($Customer, $route = null): Cookie
|
||||
{
|
||||
$expire = $this->expire;
|
||||
$cookieName = $this->cookieName;
|
||||
if ($route != null) {
|
||||
$includeRouts = $this->getIncludeRoutes();
|
||||
if (in_array($route, $includeRouts) && $this->isAuthed($Customer, 'mypage')) {
|
||||
$cookieName = $this->routeCookieName.'_'.$route;
|
||||
$expire = $this->route_expire;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createRouteAuthCookie($Customer, $cookieName, $expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 要認証ルートを取得.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIncludeRoutes(): array
|
||||
{
|
||||
$routes = [];
|
||||
$include = $this->PasskeysAuthConfig->getIncludeRoutes();
|
||||
if ($include) {
|
||||
$routes = preg_split('/\R/', $include);
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 認証済みか?
|
||||
*
|
||||
* @param Customer $Customer
|
||||
* @param null $route
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAuthed(Customer $Customer, $route = null): bool
|
||||
{
|
||||
$expire = $this->expire;
|
||||
if ($route != null) {
|
||||
$includeRouts = $this->getIncludeRoutes();
|
||||
if (in_array($route, $includeRouts) && $this->isAuthed($Customer, 'mypage')) {
|
||||
// 重要操作ルーティングの場合、
|
||||
$cookieName = $this->routeCookieName.'_'.$route;
|
||||
$expire = $this->route_expire;
|
||||
} else {
|
||||
// デフォルトルーティングの場合、
|
||||
$cookieName = $this->cookieName;
|
||||
}
|
||||
|
||||
return $this->isRouteAuthed($Customer, $cookieName, $expire);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* デフォルトルート・重要操作ルーティングは認証済みか
|
||||
* データベースの中に保存しているデータとクッキー値を比較する
|
||||
*
|
||||
* @param Customer $Customer
|
||||
* @param string $cookieName
|
||||
* @param int $expire
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRouteAuthed(Customer $Customer, string $cookieName, int $expire): bool
|
||||
{
|
||||
if ($json = $this->request->cookies->get($cookieName)) {
|
||||
$configs = json_decode($json);
|
||||
|
||||
/** @var PasskeysCustomerCookie[]|null $activeCookies */
|
||||
$activeCookies = $this
|
||||
->passkeysCustomerCookieRepository
|
||||
->searchForCookie($Customer, $cookieName);
|
||||
|
||||
foreach ($activeCookies as $activeCookie) {
|
||||
if (
|
||||
$configs
|
||||
&& isset($configs->{$Customer->getId()})
|
||||
&& ($config = $configs->{$Customer->getId()})
|
||||
&& property_exists($config, 'key')
|
||||
&& $config->key === $activeCookie->getCookieValue()
|
||||
&& (
|
||||
$this->expire == 0
|
||||
|| (property_exists($config, 'date') && ($config->date && $config->date > date('U', strtotime('-'.$expire))))
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2段階認証用Cookie生成.
|
||||
* クッキーデータをデータベースに保存する
|
||||
*
|
||||
* @param Customer $Customer
|
||||
* @param string $cookieName
|
||||
* @param int $expire
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createRouteAuthCookie(Customer $Customer, string $cookieName, int $expire)
|
||||
{
|
||||
return $this->entityManager->wrapInTransaction(function (EntityManagerInterface $em) use ($expire, $cookieName, $Customer) {
|
||||
$cookieData = $this->passkeysCustomerCookieRepository->generateCookieData(
|
||||
$Customer,
|
||||
$cookieName,
|
||||
$expire,
|
||||
$this->eccubeConfig->get('plugin_eccube_passkeys_route_cookie_value_character_length')
|
||||
);
|
||||
|
||||
$configs = json_decode('{}');
|
||||
if ($json = $this->request->cookies->get($cookieName)) {
|
||||
$configs = json_decode($json);
|
||||
}
|
||||
|
||||
$configs->{$Customer->getId()} = [
|
||||
'key' => $cookieData->getCookieValue(),
|
||||
'date' => time(),
|
||||
];
|
||||
|
||||
$em->persist($cookieData);
|
||||
$em->flush();
|
||||
|
||||
return new Cookie(
|
||||
$cookieData->getCookieName(), // name
|
||||
json_encode($configs), // value
|
||||
$cookieData->getCookieExpireDate()->getTimestamp(), // expire
|
||||
$this->request->getBasePath(), // path
|
||||
null, // domain
|
||||
$this->eccubeConfig->get('eccube_force_ssl') ? true : false, // secure
|
||||
true, // httpOnly
|
||||
false, // raw
|
||||
$this->eccubeConfig->get('eccube_force_ssl') ? Cookie::SAMESITE_NONE : null // sameSite
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 二段階認証設定が有効か?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->baseInfo->isPasskeysUse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passkey認証に関係しているクッキーだけを消す
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearPKAuthCookies(Request $request, Response $response)
|
||||
{
|
||||
foreach ($request->cookies->all() as $key => $cookie) {
|
||||
if (
|
||||
$this->str_contains($key, $this->cookieName) ||
|
||||
$this->str_contains($key, $this->routeCookieName)
|
||||
) {
|
||||
// クッキーを消す
|
||||
$response->headers->clearCookie($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passkey sessionをチェックする
|
||||
*
|
||||
* @param String $session_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkSession($session_id, $rp)
|
||||
{
|
||||
//Post request to server using curl
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://fido2.amipro.me/usr/validsession');//'https://mac-air-m2.dqj-home.com/usr/validsession');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
|
||||
//$json_body = '{"session": "'.$session_id.'", "rp": {"id": "'.$rp.'"}}';
|
||||
|
||||
$data = array(
|
||||
'session'=>$session_id,
|
||||
'rp'=> array(
|
||||
'id'=>$rp
|
||||
),
|
||||
'debug_src'=>'checkSession'
|
||||
);
|
||||
$json_body = json_encode($data);
|
||||
log_info('pk: checkSession json: ' . $json_body);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_body);//json_encode(['session' => $session_id, 'rp' => ['id' => $rp] ]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
log_info('pk: checkSession req: ' . $session_id . '|' . $rp);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
log_info('pk: checkSession resp: ' . $status_code . '|' . $response);
|
||||
$resp = json_decode($response, true);
|
||||
return $resp && $resp['status'] && $resp['status'] === 'ok';
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*
|
||||
* @deprecated ECCUBEの最低PHPバージョンは8.0になったら, この関数を消してphp8.0からのstr_containsを利用する
|
||||
*/
|
||||
private function str_contains(string $haystack, string $needle)
|
||||
{
|
||||
return $needle !== '' && mb_strpos($haystack, $needle) !== false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user