diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a4cada..9cbe5b6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: CI for TwoFactorAuthCustomerSms42 +name: CI for TwoFactorAuthCustomerSms44 on: push: branches: @@ -17,19 +17,15 @@ on: jobs: run-on-linux: name: Run on Linux - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - eccube_version: [ '4.2','4.3' ] - php: [ '7.4', '8.0', '8.1','8.2', '8.3' ] - db: [ 'mysql', 'mysql8', 'pgsql' ] - plugin_code: [ 'TwoFactorAuthCustomerSms42' ] + eccube_version: [ '4.4' ] + php: [ '8.2', '8.3', '8.4', '8.5' ] + db: [ 'mysql8', 'pgsql' ] + plugin_code: [ 'TwoFactorAuthCustomerSms44' ] include: - - db: mysql - database_url: mysql://root:password@127.0.0.1:3306/eccube_db - database_server_version: 5.7 - database_charset: utf8mb4 - db: mysql8 database_url: mysql://root:password@127.0.0.1:3308/eccube_db database_server_version: 8 @@ -38,24 +34,7 @@ jobs: database_url: postgres://postgres:password@127.0.0.1:5432/eccube_db database_server_version: 14 database_charset: utf8 - exclude: - - eccube_version: 4.2 - php: 8.2 - - eccube_version: 4.2 - php: 8.3 - - eccube_version: 4.3 - php: 7.4 - - eccube_version: 4.3 - php: 8.0 services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: ${{ matrix.dbname }} - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 mysql8: image: mysql:8 env: @@ -81,7 +60,7 @@ jobs: - 1025:1025 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: nanasess/setup-php@master @@ -97,26 +76,29 @@ jobs: - name: Checkout Base Plugin uses: actions/checkout@v4 with: - repository: 'EC-CUBE/TwoFactorAuthCustomer42' - path: 'TwoFactorAuthCustomer42' + # 親プラグイン PR が公式 4.4 にマージされるまで、パッケージ名 twofactorauthcustomer44 が入っている + # KenTanaka/TwoFactorAuthCustomer の feat-4.4 を参照する。マージ後は EC-CUBE/TwoFactorAuthCustomer42@4.4 に戻す。 + repository: 'KenTanaka/TwoFactorAuthCustomer' + ref: 'feat-4.4' + path: 'TwoFactorAuthCustomer44' - name: Archive Base Plugin - working-directory: 'TwoFactorAuthCustomer42' + working-directory: 'TwoFactorAuthCustomer44' run: | - tar cvzf ${GITHUB_WORKSPACE}/TwoFactorAuthCustomer42.tar.gz ./* + tar cvzf ${GITHUB_WORKSPACE}/TwoFactorAuthCustomer44.tar.gz ./* - name: Setup mock-package-api env: PLUGIN_CODE: ${{ matrix.plugin_code }} run: | mkdir -p /tmp/repos - for f in ${PLUGIN_CODE} TwoFactorAuthCustomer42; do + for f in ${PLUGIN_CODE} TwoFactorAuthCustomer44; do cp ${GITHUB_WORKSPACE}/${f}.tar.gz /tmp/repos/${f}.tgz done docker run --name package-api -d -v /tmp/repos:/repos -e MOCK_REPO_DIR=/repos -p 8080:8080 eccube/mock-package-api:composer2 - name: Checkout EC-CUBE - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: 'EC-CUBE/ec-cube' ref: ${{ matrix.eccube_version }} @@ -125,8 +107,8 @@ jobs: - name: Get Composer Cache Directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v1 + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -134,7 +116,9 @@ jobs: ${{ runner.os }}-composer- - name: Install to composer working-directory: 'ec-cube' - run: composer install --no-interaction -o --apcu-autoloader + # Symfony 7.4 の symfony/cache が ext-redis <6.1 と衝突するが、ランナーには + # 古い php-redis(5.3.7) が入っている。本プラグイン/本体テストは redis 未使用のため無視する。 + run: composer install --no-interaction -o --apcu-autoloader --ignore-platform-req=ext-redis - name: Setup EC-CUBE env: @@ -160,10 +144,33 @@ jobs: ECCUBE_PACKAGE_API_URL: 'http://127.0.0.1:8080' working-directory: 'ec-cube' run: | - bin/console eccube:composer:require ec-cube/twofactorauthcustomer42 - bin/console eccube:plugin:enable --code=TwoFactorAuthCustomer42 - bin/console eccube:composer:require ec-cube/twofactorauthcustomersms42 + bin/console eccube:composer:require ec-cube/twofactorauthcustomer44 + bin/console cache:clear --no-warmup + bin/console eccube:plugin:enable --code=TwoFactorAuthCustomer44 + bin/console eccube:composer:require ec-cube/twofactorauthcustomersms44 + bin/console cache:clear --no-warmup bin/console eccube:plugin:enable --code=${PLUGIN_CODE} + bin/console cache:clear --no-warmup + + - name: Run PHPUnit + env: + APP_ENV: 'test' + APP_DEBUG: 0 + DATABASE_URL: ${{ matrix.database_url }} + DATABASE_SERVER_VERSION: ${{ matrix.database_server_version }} + DATABASE_CHARSET: ${{ matrix.database_charset }} + PLUGIN_CODE: ${{ matrix.plugin_code }} + ECCUBE_PACKAGE_API_URL: 'http://127.0.0.1:8080' + working-directory: 'ec-cube' + run: | + # 有効化したプラグインのルーティングはコンテナのコンパイル時に確定する。 + # phpunit プロセスでの遅延コンパイルに任せると DB/タイミングで有効プラグイン一覧を + # 取りこぼし RouteNotFound になることがあるため、クリーンなプロセスで warmup して確定させる。 + bin/console cache:clear --no-warmup + bin/console cache:warmup + if [ -d "app/Plugin/${PLUGIN_CODE}/Tests" ]; then + ./vendor/bin/phpunit -c app/Plugin/${PLUGIN_CODE}/phpunit.xml.dist app/Plugin/${PLUGIN_CODE}/Tests + fi - name: Disable Plugin working-directory: 'ec-cube' @@ -188,3 +195,109 @@ jobs: ECCUBE_PACKAGE_API_URL: 'http://127.0.0.1:8080' working-directory: 'ec-cube' run: bin/console eccube:plugin:uninstall --code=${PLUGIN_CODE} + + static-analysis: + name: Static Analysis + runs-on: ubuntu-24.04 + # 静的解析は DB 種別に依存しないため、SQLite 1 構成で一度だけ実行する。 + # (php-cs-fixer / rector は DB 不要だが、phpstan は objectManagerLoader が + # カーネルを起動し EccubeExtension が dtb_plugin を読むため本体+DBが必要) + env: + PLUGIN_CODE: TwoFactorAuthCustomerSms44 + PARENT_PLUGIN_CODE: TwoFactorAuthCustomer44 + APP_ENV: 'test' + APP_DEBUG: 0 + DATABASE_URL: 'sqlite:///var/eccube.db' + DATABASE_SERVER_VERSION: 3 + DATABASE_CHARSET: 'utf8' + ECCUBE_PACKAGE_API_URL: 'http://127.0.0.1:8080' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: nanasess/setup-php@master + with: + php-version: '8.5' + + - name: Archive Plugin + run: | + tar cvzf ${GITHUB_WORKSPACE}/${PLUGIN_CODE}.tar.gz ./* + + - name: Checkout Base Plugin + uses: actions/checkout@v4 + with: + # 親プラグイン PR が公式 4.4 にマージされるまで、パッケージ名 twofactorauthcustomer44 が入っている + # KenTanaka/TwoFactorAuthCustomer の feat-4.4 を参照する。マージ後は EC-CUBE/TwoFactorAuthCustomer42@4.4 に戻す。 + repository: 'KenTanaka/TwoFactorAuthCustomer' + ref: 'feat-4.4' + path: 'TwoFactorAuthCustomer44' + + - name: Archive Base Plugin + working-directory: 'TwoFactorAuthCustomer44' + run: | + tar cvzf ${GITHUB_WORKSPACE}/TwoFactorAuthCustomer44.tar.gz ./* + + - name: Setup mock-package-api + run: | + mkdir -p /tmp/repos + for f in ${PLUGIN_CODE} ${PARENT_PLUGIN_CODE}; do + cp ${GITHUB_WORKSPACE}/${f}.tar.gz /tmp/repos/${f}.tgz + done + docker run --name package-api -d -v /tmp/repos:/repos -e MOCK_REPO_DIR=/repos -p 8080:8080 eccube/mock-package-api:composer2 + + - name: Checkout EC-CUBE + uses: actions/checkout@v4 + with: + repository: 'EC-CUBE/ec-cube' + ref: '4.4' + path: 'ec-cube' + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" + - uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Install to composer + working-directory: 'ec-cube' + run: composer install --no-interaction -o --apcu-autoloader --ignore-platform-req=ext-redis + + - name: Setup EC-CUBE + working-directory: 'ec-cube' + run: | + # DBAL 4 の SQLite は doctrine:database:create 非対応(getCreateDatabaseSQL が削除済み)。 + # 本体 InstallerCommand と同様、SQLite ではファイル作成をスキップし schema:create で用意する。 + if [[ "${DATABASE_URL}" != sqlite* ]]; then + bin/console doctrine:database:create + fi + bin/console doctrine:schema:create + bin/console eccube:fixtures:load + - name: Setup Plugin + working-directory: 'ec-cube' + run: | + bin/console eccube:composer:require ec-cube/twofactorauthcustomer44 + bin/console cache:clear --no-warmup + bin/console eccube:plugin:enable --code=TwoFactorAuthCustomer44 + bin/console eccube:composer:require ec-cube/twofactorauthcustomersms44 + bin/console cache:clear --no-warmup + bin/console eccube:plugin:enable --code=${PLUGIN_CODE} + bin/console cache:clear --no-warmup + + - name: Run php-cs-fixer + working-directory: 'ec-cube' + run: ./vendor/bin/php-cs-fixer fix --config=app/Plugin/${PLUGIN_CODE}/Resource/.php-cs-fixer.dist.php --dry-run --diff + + - name: Run Rector + working-directory: 'ec-cube' + run: ./vendor/bin/rector process --config=app/Plugin/${PLUGIN_CODE}/Resource/rector.php --dry-run + + - name: Run PHPStan + working-directory: 'ec-cube' + run: | + bin/console cache:clear --no-warmup + ./vendor/bin/phpstan analyse -c app/Plugin/${PLUGIN_CODE}/phpstan.neon.dist --no-progress diff --git a/Controller/TwoFactorAuthCustomerSmsController.php b/Controller/TwoFactorAuthCustomerSmsController.php index 79d4a06..54f22df 100644 --- a/Controller/TwoFactorAuthCustomerSmsController.php +++ b/Controller/TwoFactorAuthCustomerSmsController.php @@ -5,38 +5,33 @@ * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * - * http://www.ec-cube.co.jp/ + * https://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\TwoFactorAuthCustomerSms42\Controller; +namespace Plugin\TwoFactorAuthCustomerSms44\Controller; -use Eccube\Common\Constant; use Eccube\Entity\Customer; -use Plugin\TwoFactorAuthCustomer42\Controller\TwoFactorAuthCustomerController; -use Plugin\TwoFactorAuthCustomer42\Form\Type\TwoFactorAuthPhoneNumberTypeCustomer; -use Plugin\TwoFactorAuthCustomer42\Form\Type\TwoFactorAuthSmsTypeCustomer; -use Plugin\TwoFactorAuthCustomer42\Service\CustomerTwoFactorAuthService; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Plugin\TwoFactorAuthCustomer44\Controller\TwoFactorAuthCustomerController; +use Plugin\TwoFactorAuthCustomer44\Form\Type\TwoFactorAuthPhoneNumberTypeCustomer; +use Plugin\TwoFactorAuthCustomer44\Form\Type\TwoFactorAuthSmsTypeCustomer; +use Plugin\TwoFactorAuthCustomer44\Service\CustomerTwoFactorAuthService; +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Annotation\Route; -use Twig\Error\LoaderError; -use Twig\Error\RuntimeError; -use Twig\Error\SyntaxError; -use Twilio\Exceptions\ConfigurationException; -use Twilio\Exceptions\TwilioException; -use Twilio\Rest\Api\V2010\Account\MessageInstance; +use Symfony\Component\Routing\Attribute\Route; class TwoFactorAuthCustomerSmsController extends TwoFactorAuthCustomerController { /** * SMS認証 送信先入力画面. * - * @Route("/mypage/two_factor_auth/tfa/sms/send_onetime", name="plg_customer_2fa_sms_send_onetime", methods={"GET", "POST"}) - * @Template("TwoFactorAuthCustomerSms42/Resource/template/default/tfa/sms/send.twig") + * @return array|RedirectResponse */ + #[Route(path: '/mypage/two_factor_auth/tfa/sms/send_onetime', name: 'plg_customer_2fa_sms_send_onetime', methods: ['GET', 'POST'])] + #[Template('@TwoFactorAuthCustomerSms44/default/tfa/sms/send.twig')] public function inputPhoneNumber(Request $request) { if ($this->isTwoFactorAuthed()) { @@ -44,16 +39,16 @@ public function inputPhoneNumber(Request $request) } $error = null; - /** @var Customer $Customer */ $Customer = $this->getUser(); + if (!$Customer instanceof Customer) { + return $this->redirectToRoute('mypage'); + } $builder = $this->formFactory->createBuilder(TwoFactorAuthPhoneNumberTypeCustomer::class); // 入力フォーム生成 $form = $builder->getForm(); // デバイス認証済み電話番号が設定済みの場合は優先して利用 - $phoneNumber = ($Customer->getDeviceAuthedPhoneNumber() !== null) - ? $Customer->getDeviceAuthedPhoneNumber() - : $Customer->getTwoFactorAuthedPhoneNumber(); + $phoneNumber = $Customer->getDeviceAuthedPhoneNumber() ?? $Customer->getTwoFactorAuthedPhoneNumber(); if (!empty($phoneNumber)) { $form->remove('phone_number'); } @@ -62,23 +57,25 @@ public function inputPhoneNumber(Request $request) if ($form->isSubmitted() && $form->isValid()) { if ($Customer->getTwoFactorAuthType() === null || !$phoneNumber) { // 初回認証時 - $phoneNumber = $form->get('phone_number')->getData(); + $submitted = $form->get('phone_number')->getData(); + $phoneNumber = is_string($submitted) ? $submitted : ''; } - // 入力された電話番号へワンタイムコードを送信 - $this->sendToken($Customer, $phoneNumber); + if (is_string($phoneNumber) && $phoneNumber !== '') { + // 入力された電話番号へワンタイムコードを送信 + $this->sendToken($Customer, $phoneNumber); - $response = $this->redirectToRoute('plg_customer_2fa_sms_input_onetime'); + $response = $this->redirectToRoute('plg_customer_2fa_sms_input_onetime'); - // 送信電話番号をセッションへ一時格納 - $this->session->set( - CustomerTwoFactorAuthService::SESSION_AUTHED_PHONE_NUMBER, - $phoneNumber - ); + // 送信電話番号をセッションへ一時格納 + $this->session->set( + CustomerTwoFactorAuthService::SESSION_AUTHED_PHONE_NUMBER, + $phoneNumber + ); - return $response; - } else { - $error = trans('front.2fa.sms.send.failure_message'); + return $response; + } } + $error = trans('front.2fa.sms.send.failure_message'); } return [ @@ -92,9 +89,10 @@ public function inputPhoneNumber(Request $request) /** * SMS認証 ワンタイムトークン入力画面. * - * @Route("/mypage/two_factor_auth/tfa/sms/input_onetime", name="plg_customer_2fa_sms_input_onetime", methods={"GET", "POST"}) - * @Template("TwoFactorAuthCustomerSms42/Resource/template/default/tfa/sms/input.twig") + * @return array|RedirectResponse */ + #[Route(path: '/mypage/two_factor_auth/tfa/sms/input_onetime', name: 'plg_customer_2fa_sms_input_onetime', methods: ['GET', 'POST'])] + #[Template('@TwoFactorAuthCustomerSms44/default/tfa/sms/input.twig')] public function inputToken(Request $request) { if ($this->isTwoFactorAuthed()) { @@ -102,8 +100,10 @@ public function inputToken(Request $request) } $error = null; - /** @var Customer $Customer */ $Customer = $this->getUser(); + if (!$Customer instanceof Customer) { + return $this->redirectToRoute('mypage'); + } $builder = $this->formFactory->createBuilder(TwoFactorAuthSmsTypeCustomer::class); // 入力フォーム生成 $form = $builder->getForm(); @@ -111,7 +111,7 @@ public function inputToken(Request $request) $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $token = $form->get('one_time_token')->getData(); - if (!$this->checkToken($Customer, $token)) { + if (!is_string($token) || !$this->checkToken($Customer, $token)) { // ワンタイムトークン不一致 or 有効期限切れ $error = trans('front.2fa.onetime.invalid_message__reinput'); } else { @@ -119,7 +119,7 @@ public function inputToken(Request $request) $phoneNumber = $this->session->get(CustomerTwoFactorAuthService::SESSION_AUTHED_PHONE_NUMBER); // ワンタイムトークン一致 // 二段階認証完了 - $Customer->setTwoFactorAuthedPhoneNumber($phoneNumber); + $Customer->setTwoFactorAuthedPhoneNumber(is_string($phoneNumber) ? $phoneNumber : null); $this->entityManager->persist($Customer); $this->entityManager->flush(); @@ -149,18 +149,21 @@ public function inputToken(Request $request) * ワンタイムトークンチェック. * * @param Customer $Customer - * @param $token + * @param string $token * - * @return boolean + * @return bool */ - private function checkToken(Customer $Customer, $token): bool + private function checkToken(Customer $Customer, string $token): bool { $now = new \DateTime(); $hashedToken = $Customer->getTwoFactorAuthOneTimeToken(); $expire = $Customer->getTwoFactorAuthOneTimeTokenExpire(); // トークン検証 - return $this->customerTwoFactorAuthService->verifyOneTimeToken($hashedToken, $token) && $expire > $now; + return $hashedToken !== null + && $expire !== null + && $this->customerTwoFactorAuthService->verifyOneTimeToken($hashedToken, $token) + && $expire > $now; } /** @@ -168,17 +171,8 @@ private function checkToken(Customer $Customer, $token): bool * * @param Customer $Customer * @param string $phoneNumber - * - * @return MessageInstance - * - * @throws LoaderError - * @throws RuntimeError - * @throws SyntaxError - * @throws ConfigurationException - * @throws TwilioException - * @throws \Exception */ - private function sendToken($Customer, $phoneNumber) + private function sendToken(Customer $Customer, string $phoneNumber): void { // ワンタイムトークン生成・保存 $token = $this->customerTwoFactorAuthService->generateOneTimeTokenValue(); @@ -189,13 +183,13 @@ private function sendToken($Customer, $phoneNumber) $this->entityManager->flush(); // ワンタイムトークン送信メッセージをレンダリング - $twig = 'TwoFactorAuthCustomer42/Resource/template/default/sms/onetime_message.twig'; + $twig = '@TwoFactorAuthCustomer44/default/sms/onetime_message.twig'; $body = $this->twig->render($twig, [ 'Customer' => $Customer, 'token' => $token, ]); // SMS送信 - return $this->customerTwoFactorAuthService->sendBySms($phoneNumber, $body); + $this->customerTwoFactorAuthService->sendBySms($phoneNumber, $body); } } diff --git a/Entity/CustomerTrait.php b/Entity/CustomerTrait.php index fef29fd..c40d324 100644 --- a/Entity/CustomerTrait.php +++ b/Entity/CustomerTrait.php @@ -5,46 +5,40 @@ * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * - * http://www.ec-cube.co.jp/ + * https://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\TwoFactorAuthCustomerSms42\Entity; +namespace Plugin\TwoFactorAuthCustomerSms44\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; -use Eccube\Annotation\EntityExtension; +use Eccube\Attribute\EntityExtension; +use Eccube\Entity\Customer; -/** - * @EntityExtension("Eccube\Entity\Customer") - */ +#[EntityExtension(Customer::class)] trait CustomerTrait { /** * @var string|null - * - * @ORM\Column(name="two_factor_authed_phone_number", type="string", length=14, nullable=true) */ + #[ORM\Column(name: 'two_factor_authed_phone_number', type: Types::STRING, length: 14, nullable: true)] private ?string $two_factor_authed_phone_number = null; /** - * @var ?string - * - * @ORM\Column(name="two_factor_auth_one_time_token", type="string", length=255, nullable=true) + * @var string|null */ + #[ORM\Column(name: 'two_factor_auth_one_time_token', type: Types::STRING, length: 255, nullable: true)] private ?string $two_factor_auth_one_time_token = null; /** * @var \DateTime|null - * - * @ORM\Column(name="two_factor_auth_one_time_token_expire", type="datetimetz", nullable=true) */ - private $two_factor_auth_one_time_token_expire; + #[ORM\Column(name: 'two_factor_auth_one_time_token_expire', type: Types::DATETIMETZ_MUTABLE, nullable: true)] + private ?\DateTime $two_factor_auth_one_time_token_expire = null; - /** - * @return string - */ public function getTwoFactorAuthedPhoneNumber(): ?string { return $this->two_factor_authed_phone_number; @@ -58,9 +52,6 @@ public function setTwoFactorAuthedPhoneNumber(?string $two_factor_authed_phone_n $this->two_factor_authed_phone_number = $two_factor_authed_phone_number; } - /** - * @return string - */ public function getTwoFactorAuthOneTimeToken(): ?string { return $this->two_factor_auth_one_time_token; @@ -79,7 +70,7 @@ public function setTwoFactorAuthOneTimeToken(?string $two_factor_auth_one_time_t * * @return \DateTime|null */ - public function getTwoFactorAuthOneTimeTokenExpire() + public function getTwoFactorAuthOneTimeTokenExpire(): ?\DateTime { return $this->two_factor_auth_one_time_token_expire; } @@ -87,10 +78,9 @@ public function getTwoFactorAuthOneTimeTokenExpire() /** * Set oneTimeTokenExpire. * - * @param null $deviceAuthOneTimeTokenExpire * @return Customer */ - public function setTwoFactorAuthOneTimeTokenExpire($deviceAuthOneTimeTokenExpire = null) + public function setTwoFactorAuthOneTimeTokenExpire(?\DateTime $deviceAuthOneTimeTokenExpire = null): Customer { $this->two_factor_auth_one_time_token_expire = $deviceAuthOneTimeTokenExpire; diff --git a/Event.php b/Event.php index cd3fd94..f96c9e3 100644 --- a/Event.php +++ b/Event.php @@ -5,22 +5,16 @@ * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * - * http://www.ec-cube.co.jp/ + * https://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\TwoFactorAuthCustomerSms42; +namespace Plugin\TwoFactorAuthCustomerSms44; -use Doctrine\ORM\EntityManagerInterface; -use Eccube\Entity\BaseInfo; use Eccube\Event\TemplateEvent; -use Eccube\Repository\BaseInfoRepository; -use Plugin\TwoFactorAuthCustomer42\Service\CustomerTwoFactorAuthService; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Twig\Environment; /** * Class Event. @@ -28,14 +22,8 @@ class Event implements EventSubscriberInterface { /** - * Event constructor. - * + * @return array */ - public function __construct() - { - - } - public static function getSubscribedEvents(): array { return [ @@ -49,10 +37,10 @@ public static function getSubscribedEvents(): array * * @param TemplateEvent $event */ - public function onRenderAdminCustomerEdit(TemplateEvent $event) + public function onRenderAdminCustomerEdit(TemplateEvent $event): void { // add twig - $twig = 'TwoFactorAuthCustomerSms42/Resource/template/admin/customer_edit.twig'; + $twig = 'TwoFactorAuthCustomerSms44/Resource/template/admin/customer_edit.twig'; $event->addSnippet($twig); } } diff --git a/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php b/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php index d43139d..b944f34 100644 --- a/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php +++ b/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php @@ -5,13 +5,13 @@ * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * - * http://www.ec-cube.co.jp/ + * https://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\TwoFactorAuthCustomerSms42\Form\Type\Extension\Admin; +namespace Plugin\TwoFactorAuthCustomerSms44\Form\Type\Extension\Admin; use Doctrine\ORM\EntityManagerInterface; use Eccube\Form\Type\Admin\CustomerType; @@ -23,20 +23,13 @@ class TwoFactorAuthCustomerTypeExtension extends AbstractTypeExtension { - /** - * @var EntityManagerInterface - */ - protected EntityManagerInterface $entityManager; - /** * CouponDetailType constructor. * * @param EntityManagerInterface $entityManager */ - public function __construct( - EntityManagerInterface $entityManager - ) { - $this->entityManager = $entityManager; + public function __construct(protected EntityManagerInterface $entityManager) + { } /** @@ -51,9 +44,9 @@ public static function getExtendedTypes(): iterable * buildForm. * * @param FormBuilderInterface $builder - * @param array $options + * @param array $options */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { if (!empty($options['skip_add_form'])) { return; diff --git a/PluginManager.php b/PluginManager.php index b8b7a3a..b148169 100644 --- a/PluginManager.php +++ b/PluginManager.php @@ -5,22 +5,22 @@ * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * - * http://www.ec-cube.co.jp/ + * https://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\TwoFactorAuthCustomerSms42; +namespace Plugin\TwoFactorAuthCustomerSms44; use Doctrine\ORM\EntityManagerInterface; +use Eccube\Common\EccubeConfig; use Eccube\Entity\Layout; use Eccube\Entity\Page; use Eccube\Entity\PageLayout; -use Eccube\Common\EccubeConfig; use Eccube\Plugin\AbstractPluginManager; -use Plugin\TwoFactorAuthCustomer42\Entity\TwoFactorAuthConfig; -use Plugin\TwoFactorAuthCustomer42\Entity\TwoFactorAuthType; +use Plugin\TwoFactorAuthCustomer44\Entity\TwoFactorAuthConfig; +use Plugin\TwoFactorAuthCustomer44\Entity\TwoFactorAuthType; use Psr\Container\ContainerInterface; use Symfony\Component\Filesystem\Filesystem; @@ -29,17 +29,21 @@ */ class PluginManager extends AbstractPluginManager { - // 設定対象ページ情報 - private $pages = [ - ['plg_customer_2fa_sms_send_onetime', 'SMS認証送信先入力', 'TwoFactorAuthCustomer42/Resource/template/default/tfa/sms/send'], - ['plg_customer_2fa_sms_input_onetime', 'SMS認証トークン入力', 'TwoFactorAuthCustomer42/Resource/template/default/tfa/sms/input'], + /** + * 設定対象ページ情報 + * + * @var list + */ + private array $pages = [ + ['plg_customer_2fa_sms_send_onetime', 'SMS認証送信先入力', 'TwoFactorAuthCustomerSms44/Resource/template/default/tfa/sms/send'], + ['plg_customer_2fa_sms_input_onetime', 'SMS認証トークン入力', 'TwoFactorAuthCustomerSms44/Resource/template/default/tfa/sms/input'], ]; /** - * @param array $meta + * @param array $meta * @param ContainerInterface $container */ - public function enable(array $meta, ContainerInterface $container) + public function enable(array $meta, ContainerInterface $container): void { $em = $container->get('doctrine')->getManager(); @@ -53,10 +57,10 @@ public function enable(array $meta, ContainerInterface $container) } /** - * @param array $meta + * @param array $meta * @param ContainerInterface $container */ - public function disable(array $meta, ContainerInterface $container) + public function disable(array $meta, ContainerInterface $container): void { $em = $container->get('doctrine')->getManager(); @@ -71,10 +75,10 @@ public function disable(array $meta, ContainerInterface $container) } /** - * @param array $meta + * @param array $meta * @param ContainerInterface $container */ - public function uninstall(array $meta, ContainerInterface $container) + public function uninstall(array $meta, ContainerInterface $container): void { $em = $container->get('doctrine')->getManager(); @@ -93,9 +97,9 @@ public function uninstall(array $meta, ContainerInterface $container) * * @param EntityManagerInterface $em */ - protected function createConfig(EntityManagerInterface $em) + protected function createConfig(EntityManagerInterface $em): void { - /** @var TwoFactorAuthType $TwoFactorAuthType */ + /** @var TwoFactorAuthType|null $TwoFactorAuthType */ $TwoFactorAuthType = $em->getRepository(TwoFactorAuthType::class)->findOneBy(['name' => 'SMS']); if (!$TwoFactorAuthType) { // レコードを保存 @@ -111,7 +115,9 @@ protected function createConfig(EntityManagerInterface $em) // 除外ルートの登録 $TwoFactorAuthConfig = $em->find(TwoFactorAuthConfig::class, 1); - $em->persist($TwoFactorAuthConfig); + if ($TwoFactorAuthConfig !== null) { + $em->persist($TwoFactorAuthConfig); + } $em->flush(); } @@ -120,11 +126,11 @@ protected function createConfig(EntityManagerInterface $em) * * @param ContainerInterface $container */ - protected function copyTwigFiles(ContainerInterface $container) + protected function copyTwigFiles(ContainerInterface $container): void { // テンプレートファイルコピー $templatePath = $container->get(EccubeConfig::class)->get('eccube_theme_front_dir') - .'/TwoFactorAuthCustomerSms42/Resource/template/default'; + .'/TwoFactorAuthCustomerSms44/Resource/template/default'; $fs = new Filesystem(); if ($fs->exists($templatePath)) { return; @@ -138,7 +144,7 @@ protected function copyTwigFiles(ContainerInterface $container) * * @param EntityManagerInterface $em */ - protected function createPages(EntityManagerInterface $em) + protected function createPages(EntityManagerInterface $em): void { foreach ($this->pages as $p) { $hasPage = $em->getRepository(Page::class)->count(['url' => $p[0]]) > 0; @@ -155,11 +161,16 @@ protected function createPages(EntityManagerInterface $em) $em->flush(); $Layout = $em->getRepository(Layout::class)->find(Layout::DEFAULT_LAYOUT_UNDERLAYER_PAGE); + $pageId = $Page->getId(); + $layoutId = $Layout?->getId(); + if ($Layout === null || $pageId === null || $layoutId === null) { + continue; + } $PageLayout = new PageLayout(); $PageLayout->setPage($Page) - ->setPageId($Page->getId()) + ->setPageId($pageId) ->setLayout($Layout) - ->setLayoutId($Layout->getId()) + ->setLayoutId($layoutId) ->setSortNo(0); $em->persist($PageLayout); $em->flush(); @@ -174,13 +185,13 @@ protected function createPages(EntityManagerInterface $em) * * @return void */ - protected function removeConfig(EntityManagerInterface $em) + protected function removeConfig(EntityManagerInterface $em): void { /** @var TwoFactorAuthType|null $TwoFactorAuthType */ $TwoFactorAuthType = $em->getRepository(TwoFactorAuthType::class)->findOneBy(['name' => 'SMS']); // SNSオプションがあれば、そのオプションを無効にする - if (!empty($TwoFactorAuthType)) { + if ($TwoFactorAuthType !== null) { $TwoFactorAuthType->setIsDisabled(true); $em->persist($TwoFactorAuthType); } @@ -193,10 +204,10 @@ protected function removeConfig(EntityManagerInterface $em) * * @param ContainerInterface $container */ - protected function removeTwigFiles(ContainerInterface $container) + protected function removeTwigFiles(ContainerInterface $container): void { $templatePath = $container->get(EccubeConfig::class)->get('eccube_theme_front_dir') - .'/TwoFactorAuthCustomerSms42'; + .'/TwoFactorAuthCustomerSms44'; $fs = new Filesystem(); $fs->remove($templatePath); } @@ -206,15 +217,16 @@ protected function removeTwigFiles(ContainerInterface $container) * * @param EntityManagerInterface $em */ - protected function removePages(EntityManagerInterface $em) + protected function removePages(EntityManagerInterface $em): void { foreach ($this->pages as $p) { $Page = $em->getRepository(Page::class)->findOneBy(['url' => $p[0]]); - if (!$Page) { + if ($Page !== null) { $Layout = $em->getRepository(Layout::class)->find(Layout::DEFAULT_LAYOUT_UNDERLAYER_PAGE); $PageLayout = $em->getRepository(PageLayout::class)->findOneBy(['Page' => $Page, 'Layout' => $Layout]); - - $em->remove($PageLayout); + if ($PageLayout !== null) { + $em->remove($PageLayout); + } $em->remove($Page); $em->flush(); } diff --git a/Resource/.php-cs-fixer.dist.php b/Resource/.php-cs-fixer.dist.php new file mode 100644 index 0000000..79d83ee --- /dev/null +++ b/Resource/.php-cs-fixer.dist.php @@ -0,0 +1,57 @@ + true, + 'array_syntax' => ['syntax' => 'short'], + 'phpdoc_align' => false, + 'phpdoc_summary' => false, + 'phpdoc_annotation_without_dot' => false, + 'no_superfluous_phpdoc_tags' => false, + 'increment_style' => false, + 'yoda_style' => false, + 'header_comment' => ['header' => $header], + 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_param_order' => true, + 'phpdoc_to_comment' => false, // /** @var */ を変換してしまうため + 'phpdoc_trim' => true, + 'global_namespace_import' => [ + 'import_classes' => false, + 'import_constants' => false, + 'import_functions' => false, + ], + // PHPDocの型をネイティブ型へ + 'phpdoc_to_param_type' => true, + 'phpdoc_to_return_type' => true, + // プロパティのネイティブ型化は無効。EC-CUBE のテスト基盤が tearDown で全プロパティに + // null を代入するため、テストプロパティを非null native 型にすると TypeError になる。 + 'phpdoc_to_property_type' => false, +]; + +$finder = \PhpCsFixer\Finder::create() + ->in(dirname(__DIR__)) + ->exclude(['vendor', 'node_modules', 'Resource']) + ->name('*.php') +; +$config = new \PhpCsFixer\Config(); + +return $config + ->setRules($rules) + ->setFinder($finder) + ->setRiskyAllowed(true) + ->setUnsupportedPhpVersionAllowed(true) +; diff --git a/Resource/rector.php b/Resource/rector.php new file mode 100644 index 0000000..c8ee107 --- /dev/null +++ b/Resource/rector.php @@ -0,0 +1,59 @@ +withPhpVersion(PhpVersion::PHP_82) + // プラグインのソースディレクトリ + ->withPaths([ + dirname(__DIR__).'/Controller', + dirname(__DIR__).'/Entity', + dirname(__DIR__).'/Form', + dirname(__DIR__).'/Event.php', + dirname(__DIR__).'/PluginManager.php', + ]) + ->withSkip([ + dirname(__DIR__).'/vendor', + dirname(__DIR__).'/node_modules', + ]) + ->withSets([ + LevelSetList::UP_TO_PHP_82, + // rector 2.6.2 でバージョン別のセット定数 (SYMFONY_74 等) は撤去された。 + // 各ルールが composer.json を見て、インストール済みバージョンに合うものだけ実行する。 + SymfonySetList::COMPOSER_BASED, + SymfonySetList::SYMFONY_CODE_QUALITY, + DoctrineSetList::DOCTRINE_CODE_QUALITY, + DoctrineSetList::COMPOSER_BASED, + DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, + ]) + // Symfony/Doctrine 等のアノテーション → アトリビュート変換を有効化 + ->withAttributesSets() + // #[Route] は付与されるが use 文が旧 Annotation のまま残るため Attribute へ統一する + ->withConfiguredRule(RenameClassRector::class, [ + 'Symfony\Component\Routing\Annotation\Route' => 'Symfony\Component\Routing\Attribute\Route', + ]) + ->withImportNames( + importShortClasses: false, + importDocBlockNames: true, + importNames: true + ) + ->withParallel(); diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php new file mode 100644 index 0000000..45f40ee --- /dev/null +++ b/Tests/bootstrap.php @@ -0,0 +1,18 @@ +bootEnv($envFile); +} diff --git a/composer.json b/composer.json index b89732d..3523bd2 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,13 @@ { - "name": "ec-cube/twofactorauthcustomersms42", - "version": "4.3.0", - "description": "2 factor authentication for Customers(SMS) EC-CUBE42", + "name": "ec-cube/twofactorauthcustomersms44", + "version": "4.4.0", + "description": "2 factor authentication for Customers(SMS) EC-CUBE 4.4", "type": "eccube-plugin", "extra": { - "code": "TwoFactorAuthCustomerSms42" + "code": "TwoFactorAuthCustomerSms44" }, "require": { "ec-cube/plugin-installer": "^2.0", - "ec-cube/twofactorauthcustomer42": "*" + "ec-cube/twofactorauthcustomer44": "*" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..ec5115d --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,28 @@ +parameters: + level: 6 + paths: + - . + excludePaths: + - Resource/* + - Tests/bootstrap.php + doctrine: + objectManagerLoader: ../../../tests/object-manager.php + ormRepositoryClass: Eccube\Repository\AbstractRepository + # stubFiles は doctrine.objectManagerLoader が Customer/BaseInfo の + # リフレクションを上書きするため EntityExtension メソッドには効かない。 + ignoreErrors: + - + message: '#Call to an undefined method Eccube\\Entity\\Customer::(get|set|is)(DeviceAuth|DeviceAuthed|TwoFactorAuth).*#' + - + message: '#Call to an undefined method Eccube\\Entity\\BaseInfo::(is|set)(TwoFactorAuthUse|OptionActivateDevice)\(\)#' + # twilio/sdk のクラスが PHPStan のオートロードに乗らない + - + message: '#Twilio\\#' + # EntityExtension の trait は実行時 proxy に載るため、解析上は未使用になる + - + identifier: trait.unused + # EC-CUBE 本体と同様。new Entity() 時に nullable が必要なプロパティと DB 非 null の差 + - + message: "#^Property .+::\\$.+ type mapping mismatch: property can contain .+\\|null but database expects .+\\.$#" + identifier: doctrine.columnType + reportUnmatchedIgnoredErrors: false diff --git a/phpunit.xml.dist b/phpunit.xml.dist index dd5996d..274abad 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,20 @@ + - - - - - - - - + + + + + + + @@ -24,26 +24,20 @@ - - - - - + + + ./ - - ./Tests - ./Resource - ./PluginManager.php - - - + + + ./Tests + ./Resource + ./PluginManager.php + + - - - - + + + +