diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b55a392..b848952 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: CI for TwoFactorAuthCustomerApp42 +name: CI for TwoFactorAuthCustomerApp44 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: [ 'TwoFactorAuthCustomerApp42' ] + eccube_version: [ '4.4' ] + php: [ '8.2', '8.3', '8.4', '8.5' ] + db: [ 'mysql8', 'pgsql' ] + plugin_code: [ 'TwoFactorAuthCustomerApp44' ] 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/twofactorauthcustomerapp42 + 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/twofactorauthcustomerapp44 + 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: TwoFactorAuthCustomerApp44 + 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/twofactorauthcustomerapp44 + 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/.github/workflows/release.yml b/.github/workflows/release.yml index d360e34..f402a65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,11 +8,13 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Packaging working-directory: ../ run: | rm -rf $GITHUB_WORKSPACE/.github + # 開発・テスト用ファイルは配布パッケージに含めない + rm -f $GITHUB_WORKSPACE/Resource/rector.php "$GITHUB_WORKSPACE/Resource/.php-cs-fixer.dist.php" find $GITHUB_WORKSPACE -name "dummy" -delete find $GITHUB_WORKSPACE -name ".git*" -and ! -name ".gitkeep" -print0 | xargs -0 rm -rf chmod -R o+w $GITHUB_WORKSPACE diff --git a/Controller/TwoFactorAuthCustomerAppController.php b/Controller/TwoFactorAuthCustomerAppController.php index 8915c24..50c7182 100755 --- a/Controller/TwoFactorAuthCustomerAppController.php +++ b/Controller/TwoFactorAuthCustomerAppController.php @@ -5,22 +5,23 @@ * * 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\TwoFactorAuthCustomerApp42\Controller; +namespace Plugin\TwoFactorAuthCustomerApp44\Controller; use Eccube\Entity\Customer; -use Plugin\TwoFactorAuthCustomer42\Controller\TwoFactorAuthCustomerController; -use Plugin\TwoFactorAuthCustomerApp42\Form\Type\TwoFactorAuthAppTypeCustomer; +use Plugin\TwoFactorAuthCustomer44\Controller\TwoFactorAuthCustomerController; +use Plugin\TwoFactorAuthCustomerApp44\Form\Type\TwoFactorAuthAppTypeCustomer; use RobThree\Auth\TwoFactorAuth; use RobThree\Auth\TwoFactorAuthException; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; +use Symfony\Bridge\Twig\Attribute\Template; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; class TwoFactorAuthCustomerAppController extends TwoFactorAuthCustomerController { @@ -29,51 +30,47 @@ class TwoFactorAuthCustomerAppController extends TwoFactorAuthCustomerController */ protected const SESSION_APP_AUTH_KEY = 'plugin_eccube_customer_2fa_app_auth_key'; - /** - * @var TwoFactorAuth - */ - protected $tfa; - /** * 初回APP認証画面. * - * @Route("/mypage/two_factor_auth/app/create", name="plg_customer_2fa_app_create", methods={"GET", "POST"}) - * @Template("TwoFactorAuthCustomerApp42/Resource/template/default/tfa/app/register.twig") + * @return array|RedirectResponse */ + #[Route(path: '/mypage/two_factor_auth/app/create', name: 'plg_customer_2fa_app_create', methods: ['GET', 'POST'])] + #[Template('@TwoFactorAuthCustomerApp44/default/tfa/app/register.twig')] public function create(Request $request) { if ($this->isTwoFactorAuthed()) { return $this->redirectToRoute($this->getCallbackRoute()); } - $this->tfa = new TwoFactorAuth(); + $tfa = new TwoFactorAuth(); $error = null; - /** @var Customer $Customer */ $Customer = $this->getUser(); + if (!$Customer instanceof Customer) { + return $this->redirectToRoute('mypage'); + } $builder = $this->formFactory->createBuilder(TwoFactorAuthAppTypeCustomer::class); - $form = null; + $form = $builder->getForm(); $auth_key = null; if ('GET' === $request->getMethod()) { if ($Customer->getTwoFactorAuthSecret()) { // 既に二段階認証設定済み + APP認証設定済み(二回目以降) return [ - 'form' => $builder->getForm(), + 'form' => $form->createView(), 'Customer' => $Customer, ]; } - $auth_key = $this->createSecret(); + $auth_key = $this->createSecret($tfa); $this->session->set(self::SESSION_APP_AUTH_KEY, $auth_key); - $form = $builder->getForm(); } elseif ('POST' === $request->getMethod()) { - $form = $builder->getForm(); $form->handleRequest($request); $auth_key = $this->session->get(self::SESSION_APP_AUTH_KEY); if ($form->isSubmitted() && $form->isValid()) { $token = $form->get('one_time_token')->getData(); - if ($this->verifyCode($auth_key, $token)) { + if (is_string($auth_key) && is_string($token) && $this->verifyCode($tfa, $auth_key, $token)) { // 秘密鍵更新 $Customer->setTwoFactorAuthSecret($auth_key); $this->entityManager->persist($Customer); @@ -86,12 +83,12 @@ public function create(Request $request) $this->customerTwoFactorAuthService->createAuthedCookie( $Customer, $this->getCallbackRoute() - )); + ) + ); return $response; - } else { - $error = trans('front.2fa.onetime.invalid_message__reinput'); } + $error = trans('front.2fa.onetime.invalid_message__reinput'); } else { $error = trans('front.2fa.onetime.invalid_message__reinput'); } @@ -108,22 +105,25 @@ public function create(Request $request) /** * APP認証画面. * - * @Route("/mypage/two_factor_auth/app/challenge", name="plg_customer_2fa_app_challenge", methods={"GET", "POST"}) - * @Template("TwoFactorAuthCustomerApp42/Resource/template/default/tfa/app/challenge.twig") + * @return array|RedirectResponse */ + #[Route(path: '/mypage/two_factor_auth/app/challenge', name: 'plg_customer_2fa_app_challenge', methods: ['GET', 'POST'])] + #[Template('@TwoFactorAuthCustomerApp44/default/tfa/app/challenge.twig')] public function challenge(Request $request) { if ($this->isTwoFactorAuthed()) { return $this->redirectToRoute($this->getCallbackRoute()); } - $this->tfa = new TwoFactorAuth(); + $tfa = new TwoFactorAuth(); $error = null; - /** @var Customer $Customer */ $Customer = $this->getUser(); + if (!$Customer instanceof Customer) { + return $this->redirectToRoute('mypage'); + } - if ($Customer->getTwoFactorAuthSecret() == null) { + if ($Customer->getTwoFactorAuthSecret() === null) { // APP認証設定まだ return $this->redirectToRoute('plg_customer_2fa_app_create'); } @@ -135,7 +135,9 @@ public function challenge(Request $request) if ('POST' === $request->getMethod()) { $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - if ($this->verifyCode($Customer->getTwoFactorAuthSecret(), $form->get('one_time_token')->getData())) { + $secret = $Customer->getTwoFactorAuthSecret(); + $token = $form->get('one_time_token')->getData(); + if (is_string($secret) && is_string($token) && $this->verifyCode($tfa, $secret, $token)) { $response = $this->redirectToRoute($this->getCallbackRoute()); $response->headers->setCookie( $this->customerTwoFactorAuthService->createAuthedCookie( @@ -145,9 +147,8 @@ public function challenge(Request $request) ); return $response; - } else { - $error = trans('front.2fa.onetime.invalid_message__reinput'); } + $error = trans('front.2fa.onetime.invalid_message__reinput'); } else { $error = trans('front.2fa.onetime.invalid_message__reinput'); } @@ -159,29 +160,27 @@ public function challenge(Request $request) ]; } - /** * 秘密鍵生成. * - * @return string + * @param TwoFactorAuth $tfa * * @throws TwoFactorAuthException */ - private function createSecret() + private function createSecret(TwoFactorAuth $tfa): string { - return $this->tfa->createSecret(); + return $tfa->createSecret(); } /** - * 認証コードを取得. + * 認証コードを検証. * + * @param TwoFactorAuth $tfa * @param string $authKey * @param string $token - * - * @return boolean */ - private function verifyCode($authKey, $token) + private function verifyCode(TwoFactorAuth $tfa, string $authKey, string $token): bool { - return $this->tfa->verifyCode($authKey, $token, 1); + return $tfa->verifyCode($authKey, $token, 1); } } diff --git a/Entity/CustomerTrait.php b/Entity/CustomerTrait.php index 8bcc126..4252064 100644 --- a/Entity/CustomerTrait.php +++ b/Entity/CustomerTrait.php @@ -5,40 +5,30 @@ * * 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\TwoFactorAuthCustomerApp42\Entity; +namespace Plugin\TwoFactorAuthCustomerApp44\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 - * - * @ORM\Column(name="two_factor_auth_secret", type="string", length=255, nullable=true) - */ + #[ORM\Column(name: 'two_factor_auth_secret', type: Types::STRING, length: 255, nullable: true)] private ?string $two_factor_auth_secret = null; - /** - * @return string - */ public function getTwoFactorAuthSecret(): ?string { return $this->two_factor_auth_secret; } - /** - * @param string|null $two_factor_auth_secret - */ public function setTwoFactorAuthSecret(?string $two_factor_auth_secret): void { $this->two_factor_auth_secret = $two_factor_auth_secret; diff --git a/Event.php b/Event.php index 0856be5..71e2e2e 100644 --- a/Event.php +++ b/Event.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\TwoFactorAuthCustomerApp42; +namespace Plugin\TwoFactorAuthCustomerApp44; use Eccube\Event\TemplateEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -22,14 +22,8 @@ class Event implements EventSubscriberInterface { /** - * Event constructor. - * + * @return array */ - public function __construct() - { - - } - public static function getSubscribedEvents(): array { return [ @@ -43,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 = 'TwoFactorAuthCustomerApp42/Resource/template/admin/customer_edit.twig'; + $twig = 'TwoFactorAuthCustomerApp44/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 7a30682..422f63d 100644 --- a/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php +++ b/Form/Type/Extension/Admin/TwoFactorAuthCustomerTypeExtension.php @@ -5,15 +5,14 @@ * * 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\TwoFactorAuthCustomerApp42\Form\Type\Extension\Admin; +namespace Plugin\TwoFactorAuthCustomerApp44\Form\Type\Extension\Admin; -use Doctrine\ORM\EntityManagerInterface; use Eccube\Form\Type\Admin\CustomerType; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -23,29 +22,13 @@ class TwoFactorAuthCustomerTypeExtension extends AbstractTypeExtension { - /** - * @var EntityManagerInterface - */ - protected EntityManagerInterface $entityManager; - - /** - * CouponDetailType constructor. - * - * @param EntityManagerInterface $entityManager - */ - public function __construct( - EntityManagerInterface $entityManager - ) { - $this->entityManager = $entityManager; - } - /** * 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/Form/Type/TwoFactorAuthAppTypeCustomer.php b/Form/Type/TwoFactorAuthAppTypeCustomer.php index 9adf0cd..2775b48 100644 --- a/Form/Type/TwoFactorAuthAppTypeCustomer.php +++ b/Form/Type/TwoFactorAuthAppTypeCustomer.php @@ -5,16 +5,15 @@ * * 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\TwoFactorAuthCustomerApp42\Form\Type; +namespace Plugin\TwoFactorAuthCustomerApp44\Form\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints as Assert; @@ -22,33 +21,33 @@ class TwoFactorAuthAppTypeCustomer extends AbstractType { /** - * {@inheritdoc} + * buildForm. + * + * @param FormBuilderInterface $builder + * @param array $options */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add( 'one_time_token', TextType::class, [ - 'label' => 'front.setting.system.two_factor_auth.device_token', - 'required' => true, - 'constraints' => [ - new Assert\NotBlank(), - new Assert\Length([ - 'max' => 6, - 'min' => 6, - ]), - ], - 'attr' => [ - 'maxlength' => 6, - 'style' => 'width: 100px;', - ], - ]); + 'label' => 'front.setting.system.two_factor_auth.device_token', + 'required' => true, + 'constraints' => [ + new Assert\NotBlank(), + new Assert\Length(max: 6, min: 6), + ], + 'attr' => [ + 'maxlength' => 6, + 'style' => 'width: 100px;', + ], + ]); } /** * {@inheritdoc} */ - public function getBlockPrefix() + public function getBlockPrefix(): string { return 'plg_customer_2fa'; } diff --git a/PluginManager.php b/PluginManager.php index 005afed..72a0068 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\TwoFactorAuthCustomerApp42; +namespace Plugin\TwoFactorAuthCustomerApp44; use Doctrine\ORM\EntityManagerInterface; +use Eccube\Common\EccubeConfig; use Eccube\Entity\Layout; use Eccube\Entity\Page; use Eccube\Entity\PageLayout; use Eccube\Plugin\AbstractPluginManager; -use Eccube\Common\EccubeConfig; -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_app_create', 'アプリ認証初期設定・トークン入力', 'TwoFactorAuthCustomer42/Resource/template/default/tfa/app/register'], - ['plg_customer_2fa_app_challenge', 'アプリ認証トークン入力', 'TwoFactorAuthCustomer42/Resource/template/default/tfa/app/challenge'], + /** + * 設定対象ページ情報 + * + * @var list + */ + private array $pages = [ + ['plg_customer_2fa_app_create', 'アプリ認証初期設定・トークン入力', 'TwoFactorAuthCustomerApp44/Resource/template/default/tfa/app/register'], + ['plg_customer_2fa_app_challenge', 'アプリ認証トークン入力', 'TwoFactorAuthCustomerApp44/Resource/template/default/tfa/app/challenge'], ]; /** - * @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,11 +97,11 @@ public function uninstall(array $meta, ContainerInterface $container) * * @param ContainerInterface $container */ - protected function copyTwigFiles(ContainerInterface $container) + protected function copyTwigFiles(ContainerInterface $container): void { // テンプレートファイルコピー $templatePath = $container->get(EccubeConfig::class)->get('eccube_theme_front_dir') - .'/TwoFactorAuthCustomerApp42/Resource/template/default'; + .'/TwoFactorAuthCustomerApp44/Resource/template/default'; $fs = new Filesystem(); if ($fs->exists($templatePath)) { return; @@ -111,12 +115,12 @@ 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; if (!$hasPage) { - /** @var \Eccube\Entity\Page $Page */ + /** @var Page $Page */ $Page = $em->getRepository(Page::class)->newPage(); $Page->setEditType(Page::EDIT_TYPE_DEFAULT); $Page->setUrl($p[0]); @@ -128,11 +132,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(); @@ -145,10 +154,10 @@ protected function createPages(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') - .'/TwoFactorAuthCustomerApp42'; + .'/TwoFactorAuthCustomerApp44'; $fs = new Filesystem(); $fs->remove($templatePath); } @@ -158,15 +167,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(); } @@ -178,9 +188,9 @@ protected function removePages(EntityManagerInterface $em) * * @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' => 'APP']); if (!$TwoFactorAuthType) { // レコードを保存 @@ -197,26 +207,24 @@ protected function createConfig(EntityManagerInterface $em) // 除外ルートの登録 $TwoFactorAuthConfig = $em->find(TwoFactorAuthConfig::class, 1); - $em->persist($TwoFactorAuthConfig); + if ($TwoFactorAuthConfig !== null) { + $em->persist($TwoFactorAuthConfig); + } $em->flush(); - - return; } /** * 2段階認証設定を消す * * @param 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' => 'APP']); // APPオプションがあれば、そのオプションを無効にする - if (!empty($TwoFactorAuthType)) { + if ($TwoFactorAuthType !== null) { $TwoFactorAuthType->setIsDisabled(true); $em->persist($TwoFactorAuthType); } diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2e4016 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# TwoFactorAuthCustomerApp44 + +EC-CUBE 4.4(Symfony 7.4)向けの会員向けアプリ認証(TOTP)プラグインです。親プラグイン `TwoFactorAuthCustomer44` が必要です。 + +- プラグインコード: `TwoFactorAuthCustomerApp44` +- Composer: `ec-cube/twofactorauthcustomerapp44` +- 依存: `ec-cube/twofactorauthcustomer44` + +## ブランチ運用 + +- `4.2` / `4.3`: `TwoFactorAuthCustomerApp42` +- `4.4`: `TwoFactorAuthCustomerApp44`(Attribute / ORM 3 / PHP 8.2+) 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 10b8bd5..3cdf1f7 100644 --- a/composer.json +++ b/composer.json @@ -1,13 +1,13 @@ { - "name": "ec-cube/twofactorauthcustomerapp42", - "version": "4.3.0", - "description": "2 factor authentication for Customers(APP) EC-CUBE42", + "name": "ec-cube/twofactorauthcustomerapp44", + "version": "4.4.0", + "description": "2 factor authentication for Customers(APP) EC-CUBE 4.4", "type": "eccube-plugin", "extra": { - "code": "TwoFactorAuthCustomerApp42" + "code": "TwoFactorAuthCustomerApp44" }, "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..f699510 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,25 @@ +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)\(\)#' + # 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 + + - - - - + + + +