diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 22c413c..422b838 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -25,24 +25,28 @@ jobs: - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' run: composer install --prefer-dist --no-progress --no-suggest + - name: Run php-cs-fixer + run: PHP_CS_FIXER_IGNORE_ENV=1 tools/php-cs-fixer fix --dry-run --diff --ansi + - name: Run psalm + run: tools/psalm test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - description: 'PHP 8.0 - SF 5.4.*' - php: '8.0' - symfony_version: '5.4.*' - description: 'PHP 8.1 - SF 5.4.*' php: '8.1' symfony_version: '5.4.*' - - description: 'PHP 8.0 - SF 6.0.*' - php: '8.0' - symfony_version: '6.0.*' - - description: 'PHP 8.1 - SF 6.0.*' - php: '8.1' - symfony_version: '6.0.*' + - description: 'PHP 8.3 - SF 6.3.*' + php: '8.3' + symfony_version: '6.3.*' + - description: 'PHP 8.3 - SF 6.4.*' + php: '8.3' + symfony_version: '6.4.*' + - description: 'PHP 8.3 - SF 7.0' + php: '8.3' + symfony_version: '7.0.*' name: Tests ${{ matrix.description }} steps: - name: Checkout @@ -63,12 +67,8 @@ jobs: php-version: ${{ matrix.php }} coverage: "pcov" - run: | - sed -ri 's/"symfony\/(.+)": "(.+)"/"symfony\/\1": "'${{ matrix.symfony_version }}'"/' composer.json; + sed -Ei '/"symfony\/var-exporter":/! s/"symfony\/(.+)": "(.+)"/"symfony\/\1": "'${{ matrix.symfony_version }}'"/' composer.json; - run: composer update --no-interaction --no-progress --ansi ${{ matrix.composer_flags }} - - name: Run php-cs-fixer - run: tools/php-cs-fixer fix --dry-run --diff --ansi - - name: Run psalm - run: tools/psalm - name: Run Tests run: vendor/bin/phpunit --coverage-clover=coverage.xml - name: Upload coverage to Codecov diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f97aa05 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Change Log +All notable changes to this project will be documented in this file (after version 4.0). + +## [4.1.0] - 2024-01-09 + +Run composer update + +### Added + +- Add attribute support for the Tenant entity + - This allows you to drop support for the annotations driver for doctrine. +- Check compatibility with Symfonh 6.4 +- Add support for Symfony 7.0 + +### Changed + +### Fixed + +### Removed + +- Dropped support for PHP 8.0 (symfony var-exporter) +- Dropped support for Symfony 6.1 and 6.2 (unmaintained versions. Upgrade immediately) \ No newline at end of file diff --git a/Tests/Functional/Repository/TenantRepositoryTest.php b/Tests/Functional/Repository/TenantRepositoryTest.php index 5b35798..d88dda3 100644 --- a/Tests/Functional/Repository/TenantRepositoryTest.php +++ b/Tests/Functional/Repository/TenantRepositoryTest.php @@ -33,8 +33,10 @@ public function testFindTenantByClientKey(): void public function testSaveTenant(): void { self::bootKernel(); + /** @var TenantRepositoryInterface $repository */ $repository = self::getContainer()->get(TenantRepositoryInterface::class); + /** @var Tenant $tenant */ $tenant = $repository->initializeTenant(); $tenant->setClientKey('new_client_key'); $tenant->setAddonKey('key'); @@ -48,8 +50,9 @@ public function testSaveTenant(): void $tenant->setEventType('event'); $repository->save($tenant); - self::getContainer()->get(EntityManagerInterface::class)->clear(Tenant::class); + self::getContainer()->get(EntityManagerInterface::class)->clear(); - $this->assertNotNull($repository->findByClientKey('new_client_key')); + $this->assertNotNull($tenant = $repository->findByClientKey('new_client_key')); + $this->assertNotNull($tenant->getCreatedAt()); } } diff --git a/Tests/Functional/app/config/base/security.yaml b/Tests/Functional/app/config/base/security.yaml index d8e545d..e0e30c7 100644 --- a/Tests/Functional/app/config/base/security.yaml +++ b/Tests/Functional/app/config/base/security.yaml @@ -1,5 +1,4 @@ security: - enable_authenticator_manager: true providers: jwt_user_provider: id: jwt_user_provider diff --git a/Tests/Functional/app/config/packages/doctrine.yaml b/Tests/Functional/app/config/packages/doctrine.yaml index 6e965c2..bc1db7d 100644 --- a/Tests/Functional/app/config/packages/doctrine.yaml +++ b/Tests/Functional/app/config/packages/doctrine.yaml @@ -3,6 +3,8 @@ doctrine: driver: pdo_sqlite path: "%kernel.cache_dir%/test-database.sqlite" orm: + enable_lazy_ghost_objects: true + report_fields_where_declared: true auto_generate_proxy_classes: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true diff --git a/Tests/Functional/app/config/routes.yaml b/Tests/Functional/app/config/routes.yaml index 5595381..dc1d11d 100644 --- a/Tests/Functional/app/config/routes.yaml +++ b/Tests/Functional/app/config/routes.yaml @@ -1,6 +1,12 @@ -controllers: - resource: ../src/Controller/ - type: annotation +protected: + path: /protected/route + controller: AtlassianConnectBundle\Tests\Functional\App\Controller\ProtectedController::protectedRoute + +license_protected: + path: /protected/license-route + defaults: + requires_license: true + controller: AtlassianConnectBundle\Tests\Functional\App\Controller\ProtectedController::licenseProtectedRoute ac: resource: "@AtlassianConnectBundle/Resources/config/routing.php" diff --git a/Tests/Functional/app/src/Controller/ProtectedController.php b/Tests/Functional/app/src/Controller/ProtectedController.php index c37ea40..8586e52 100644 --- a/Tests/Functional/app/src/Controller/ProtectedController.php +++ b/Tests/Functional/app/src/Controller/ProtectedController.php @@ -6,21 +6,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; final class ProtectedController extends AbstractController { - /** - * @Route("/protected/route") - */ public function protectedRoute(): Response { return new Response('OK'); } - /** - * @Route("/protected/license-route", defaults={"requires_license": "true"}) - */ public function licenseProtectedRoute(): Response { return new Response('OK'); diff --git a/Tests/Functional/app/src/Kernel.php b/Tests/Functional/app/src/Kernel.php index 8c18d34..f8c4a29 100644 --- a/Tests/Functional/app/src/Kernel.php +++ b/Tests/Functional/app/src/Kernel.php @@ -63,6 +63,10 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa protected function build(ContainerBuilder $container): void { + if (self::MAJOR_VERSION < 6) { + $container->prependExtensionConfig('security', ['enable_authenticator_manager' => true]); + } + $container->register('logger', NullLogger::class); } } diff --git a/Tests/Listener/LicenseListenerTest.php b/Tests/Listener/LicenseListenerTest.php index c03134e..5d6e9b2 100644 --- a/Tests/Listener/LicenseListenerTest.php +++ b/Tests/Listener/LicenseListenerTest.php @@ -8,7 +8,6 @@ use AtlassianConnectBundle\Listener\LicenseListener; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -33,13 +32,7 @@ protected function setUp(): void public function testItSkipsOnASubRequest(): void { - $attributeParameterBag = $this->createMock(ParameterBagInterface::class); - $attributeParameterBag - ->expects($this->never()) - ->method('get'); - - $request = new Request(); - $request->attributes = $attributeParameterBag; + $request = new Request([], [], []); $event = $this->getEvent( $this->kernel, @@ -48,6 +41,7 @@ public function testItSkipsOnASubRequest(): void ); $this->getLicenseListener()->onKernelRequest($event); + $this->assertNull($event->getResponse()); } public function testItSkipsWhenTheRouteIsNotNullAndRouteRequiresNoLicense(): void @@ -85,7 +79,7 @@ public function testItSkipsWhenTheRouteIsNotNullAndRouteHasNoRequiresLicenseAttr $event = $this->getEvent( $this->kernel, $request, - KernelInterface::MASTER_REQUEST + KernelInterface::MAIN_REQUEST ); $this->getLicenseListener()->onKernelRequest($event); diff --git a/Tests/Service/AtlassianRestClientTest.php b/Tests/Service/AtlassianRestClientTest.php index a8751b8..17c0d4b 100644 --- a/Tests/Service/AtlassianRestClientTest.php +++ b/Tests/Service/AtlassianRestClientTest.php @@ -151,7 +151,7 @@ public function testGetTenantFromTokenStorage(): void public function testNoTenantInToken(): void { $this->expectException(\RuntimeException::class); - $this->expectDeprecationMessage('Could not get tenant from token'); + $this->expectExceptionMessage('Could not get tenant from token'); $this->tokenStorage ->expects($this->once()) @@ -164,7 +164,7 @@ public function testNoTenantInToken(): void public function testNotInTenantContext(): void { $this->expectException(\RuntimeException::class); - $this->expectDeprecationMessage('Current user is not a Tenant'); + $this->expectExceptionMessage('Current user is not a Tenant'); $this->tokenStorage ->expects($this->once()) diff --git a/composer.json b/composer.json index e07bda4..8b5ca0c 100644 --- a/composer.json +++ b/composer.json @@ -6,28 +6,29 @@ "type": "symfony-bundle", "license": "MIT", "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/config": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.3|^7.0", + "symfony/config": "^5.4|^6.3|^7.0", + "symfony/http-client": "^5.4|^6.3|^7.0", + "symfony/yaml": "^5.4|^6.3|^7.0", + "symfony/security-bundle": "^5.4|^6.3|^7.0", + "symfony/routing": "^5.4|^6.3|^7.0", + "symfony/console": "^5.4|^6.3|^7.0", "doctrine/orm": "^2.5", "twig/twig": "^2.10|^3.0", "firebase/php-jwt": "^6.2" }, "require-dev": { "phpunit/phpunit": "^9.5.10", - "symfony/framework-bundle": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.3|^7.0", "doctrine/doctrine-bundle": "^1.12|^2.5", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", + "symfony/browser-kit": "^5.4|^6.3|^7.0", + "symfony/twig-bundle": "^5.4|^6.3|^7.0", "doctrine/doctrine-fixtures-bundle": "^2.3|^3.0", - "symfony/phpunit-bridge": "^5.4|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.3|^7.0", + "symfony/var-exporter": "^6.3|7.0" }, "authors": [ { diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 74465db..75a3d59 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -9,6 +9,10 @@ class Configuration implements ConfigurationInterface { + /** + * @psalm-suppress UndefinedInterfaceMethod + * @psalm-suppress UndefinedMethod + */ public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('atlassian_connect'); diff --git a/src/Entity/Tenant.php b/src/Entity/Tenant.php index 82d46da..af86142 100644 --- a/src/Entity/Tenant.php +++ b/src/Entity/Tenant.php @@ -11,6 +11,7 @@ * @ORM\HasLifecycleCallbacks() * @ORM\Entity() */ +#[ORM\Entity, ORM\Table(name: 'tenant'), ORM\HasLifecycleCallbacks] class Tenant implements TenantInterface { use TenantTrait; diff --git a/src/Entity/TenantTrait.php b/src/Entity/TenantTrait.php index 14abebd..714cd0b 100644 --- a/src/Entity/TenantTrait.php +++ b/src/Entity/TenantTrait.php @@ -4,6 +4,7 @@ namespace AtlassianConnectBundle\Entity; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; trait TenantTrait @@ -15,6 +16,7 @@ trait TenantTrait * @ORM\Id() * @ORM\GeneratedValue(strategy="AUTO") */ + #[ORM\Column(name: 'id', type: Types::INTEGER), ORM\Id, ORM\GeneratedValue(strategy: 'AUTO')] private $id; /** @@ -22,6 +24,7 @@ trait TenantTrait * * @ORM\Column(name="addon_key", type="string", length=255) */ + #[ORM\Column(name: 'addon_key', type: Types::STRING, length: 255)] private $addonKey; /** @@ -29,6 +32,7 @@ trait TenantTrait * * @ORM\Column(name="client_key", type="string", length=255, unique=true) */ + #[ORM\Column(name: 'client_key', type: Types::STRING, length: 255, unique: true)] private $clientKey; /** @@ -36,6 +40,7 @@ trait TenantTrait * * @ORM\Column(name="oauth_client_id", type="string", length=255, nullable=true) */ + #[ORM\Column(name: 'oauth_client_id', type: Types::STRING, length: 255, nullable: true)] private $oauthClientId; /** @@ -48,6 +53,7 @@ trait TenantTrait * * @ORM\Column(name="public_key", type="string", length=255) */ + #[ORM\Column(name: 'public_key', type: Types::STRING, length: 255)] private $publicKey; /** @@ -55,6 +61,7 @@ trait TenantTrait * * @ORM\Column(name="shared_secret", type="string", length=255) */ + #[ORM\Column(name: 'shared_secret', type: Types::STRING, length: 255)] private $sharedSecret; /** @@ -62,6 +69,7 @@ trait TenantTrait * * @ORM\Column(name="server_version", type="string", length=255) */ + #[ORM\Column(name: 'server_version', type: Types::STRING, length: 255)] private $serverVersion; /** @@ -69,6 +77,7 @@ trait TenantTrait * * @ORM\Column(name="plugins_version", type="string", length=255) */ + #[ORM\Column(name: 'plugins_version', type: Types::STRING, length: 255)] private $pluginsVersion; /** @@ -76,6 +85,7 @@ trait TenantTrait * * @ORM\Column(name="base_url", type="string", length=255) */ + #[ORM\Column(name: 'base_url', type: Types::STRING, length: 255)] private $baseUrl; /** @@ -83,6 +93,7 @@ trait TenantTrait * * @ORM\Column(name="product_type", type="string", length=255) */ + #[ORM\Column(name: 'product_type', type: Types::STRING, length: 255)] private $productType; /** @@ -90,6 +101,7 @@ trait TenantTrait * * @ORM\Column(name="description", type="string", length=255) */ + #[ORM\Column(name: 'description', type: Types::STRING, length: 255)] private $description; /** @@ -97,6 +109,7 @@ trait TenantTrait * * @ORM\Column(name="event_type", type="string", length=255) */ + #[ORM\Column(name: 'event_type', type: Types::STRING, length: 255)] private $eventType; /** @@ -104,6 +117,7 @@ trait TenantTrait * * @ORM\Column(name="created_at", type="datetime", nullable=false) */ + #[ORM\Column(name: 'created_at', type: Types::DATETIME_MUTABLE, nullable: false)] private $createdAt; /** @@ -111,6 +125,7 @@ trait TenantTrait * * @ORM\Column(name="updated_at", type="datetime", nullable=false) */ + #[ORM\Column(name: 'updated_at', type: Types::DATETIME_MUTABLE, nullable: false)] private $updatedAt; /** @@ -118,6 +133,7 @@ trait TenantTrait * * @ORM\Column(name="is_white_listed", type="boolean", options={"default":0}) */ + #[ORM\Column(name: 'is_white_listed', type: Types::BOOLEAN, options: ['default' => 0])] private $isWhiteListed = false; /** @@ -125,11 +141,13 @@ trait TenantTrait * * @ORM\Column(name="white_listed_until", type="datetime", nullable=true) */ + #[ORM\Column(name: 'white_listed_until', type: Types::DATETIME_MUTABLE, nullable: true)] private $whiteListedUntil; /** * @ORM\PrePersist() */ + #[ORM\PrePersist] public function setCreatedAt(): void { $this->createdAt = new \DateTime(); @@ -144,6 +162,7 @@ public function getCreatedAt(): \DateTime /** * @ORM\PreUpdate() */ + #[ORM\PreUpdate] public function setUpdatedAt(): void { $this->updatedAt = new \DateTime(); diff --git a/src/Security/JWTUserProvider.php b/src/Security/JWTUserProvider.php index 79ec5d7..f297c7e 100644 --- a/src/Security/JWTUserProvider.php +++ b/src/Security/JWTUserProvider.php @@ -13,6 +13,9 @@ use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @template-implements JWTUserProviderInterface + */ class JWTUserProvider implements JWTUserProviderInterface { public function __construct(private TenantRepositoryInterface $repository) @@ -38,7 +41,7 @@ public function loadUserByUsername(string $username): UserInterface return $this->loadUserByIdentifier($username); } - public function refreshUser(UserInterface $user): void + public function refreshUser(UserInterface $user): UserInterface { throw new UnsupportedUserException('Refresh prohibited'); } diff --git a/src/Security/JWTUserProviderInterface.php b/src/Security/JWTUserProviderInterface.php index 7ae2c9f..1c45df1 100644 --- a/src/Security/JWTUserProviderInterface.php +++ b/src/Security/JWTUserProviderInterface.php @@ -4,8 +4,13 @@ namespace AtlassianConnectBundle\Security; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +/** + * @template-covariant TUser of UserInterface + * @template-extends UserProviderInterface + */ interface JWTUserProviderInterface extends UserProviderInterface { public function getDecodedToken(string $jwt): object; diff --git a/tools/psalm b/tools/psalm index 52d447d..5df97c1 100755 Binary files a/tools/psalm and b/tools/psalm differ