Init Gitea

This commit is contained in:
dqj
2025-10-06 21:45:33 +09:00
commit c8607205a2
33 changed files with 4268 additions and 0 deletions

View 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;
}
/**
* 段階認証用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;
}
}