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; } }