From 5c75500d96bd4a03e13a26d65f2cdc1dbd492deb Mon Sep 17 00:00:00 2001 From: konradoboza Date: Tue, 28 May 2024 11:16:57 +0200 Subject: [PATCH] IBX-8290: Reworked REST authentication to comply with the new Symfony authenticator mechanism under separate firewall --- dependencies.json | 4 + phpstan-baseline-8.0.neon | 20 - phpstan-baseline.neon | 1110 +---------------- .../Security/RestSessionBasedFactory.php | 109 -- src/bundle/EventListener/CsrfListener.php | 111 +- src/bundle/EventListener/RequestListener.php | 26 +- src/bundle/EventListener/ResponseListener.php | 24 +- src/bundle/IbexaRestBundle.php | 5 - src/bundle/Resources/config/routing.yml | 14 +- src/bundle/Resources/config/security.yml | 23 +- src/bundle/Resources/config/services.yml | 11 +- src/lib/Input/FieldTypeParser.php | 2 +- .../Authenticator/RestAuthenticator.php | 127 ++ .../Server/Controller/SessionController.php | 227 ++-- src/lib/Server/Controller/User.php | 307 +---- src/lib/Server/Security/CsrfTokenManager.php | 31 +- .../EventListener/SecurityListener.php | 3 +- src/lib/Server/Security/RestAuthenticator.php | 219 ---- src/lib/Server/Security/RestLogoutHandler.php | 59 - src/lib/Server/Values/DeletedUserSession.php | 12 +- src/lib/Server/Values/SessionInput.php | 16 +- src/lib/Server/Values/UserSession.php | 56 +- .../bundle/EventListener/CsrfListenerTest.php | 405 +++--- .../EventListener/EventListenerTest.php | 120 +- .../EventListener/RequestListenerTest.php | 9 +- .../EventListener/ResponseListenerTest.php | 127 +- tests/bundle/Functional/BinaryContentTest.php | 14 +- tests/bundle/Functional/SessionTest.php | 116 +- tests/bundle/Functional/UserTest.php | 80 +- tests/integration/UriParser/UriParserTest.php | 21 +- .../Parser/FieldDefinitionUpdateTest.php | 51 +- .../Server/Security/CsrfTokenManagerTest.php | 77 -- .../Server/Security/RestLogoutHandlerTest.php | 123 -- .../RestSessionBasedAuthenticatorTest.php | 572 --------- 34 files changed, 781 insertions(+), 3450 deletions(-) create mode 100644 dependencies.json delete mode 100644 src/bundle/DependencyInjection/Security/RestSessionBasedFactory.php create mode 100644 src/lib/Security/Authenticator/RestAuthenticator.php delete mode 100644 src/lib/Server/Security/RestAuthenticator.php delete mode 100644 src/lib/Server/Security/RestLogoutHandler.php delete mode 100644 tests/lib/Server/Security/CsrfTokenManagerTest.php delete mode 100644 tests/lib/Server/Security/RestLogoutHandlerTest.php delete mode 100644 tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 00000000..91fe917f --- /dev/null +++ b/dependencies.json @@ -0,0 +1,4 @@ +{ + "recipesEndpoint": "https://api.github.com/repos/ibexa/recipes-dev/contents/index.json?ref=flex/pull-121", + "packages": [] +} diff --git a/phpstan-baseline-8.0.neon b/phpstan-baseline-8.0.neon index bacc8657..39205d06 100644 --- a/phpstan-baseline-8.0.neon +++ b/phpstan-baseline-8.0.neon @@ -1,10 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Parameter \\#1 \\$filename of function file_put_contents expects string, string\\|false given\\.$#" - count: 1 - path: src/lib/FieldTypeProcessor/BinaryInputProcessor.php - - message: "#^Access to an undefined property DOMNode\\:\\:\\$data\\.$#" count: 2 @@ -60,22 +55,7 @@ parameters: count: 1 path: src/lib/Server/Controller/Role.php - - - message: "#^Parameter \\#2 \\$error_level of function trigger_error expects int, string given\\.$#" - count: 4 - path: src/lib/Server/Controller/User.php - - - - message: "#^Parameter \\#1 \\$message of method Psr\\\\Log\\\\LoggerInterface\\:\\:error\\(\\) expects string\\|Stringable, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - message: "#^Parameter \\#1 \\$string of function base64_encode expects string, string\\|false given\\.$#" count: 1 path: tests/bundle/Functional/BinaryContentTest.php - - - - message: "#^Parameter \\#1 \\$directory of function mkdir expects string, string\\|false given\\.$#" - count: 1 - path: tests/lib/FieldTypeProcessor/BinaryInputProcessorTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 18c5afc1..6f86bb0e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -85,56 +85,6 @@ parameters: count: 1 path: src/bundle/DependencyInjection/IbexaRestExtension.php - - - message: "#^Cannot call method isTokenValid\\(\\) on Symfony\\\\Component\\\\Security\\\\Csrf\\\\CsrfTokenManagerInterface\\|null\\.$#" - count: 1 - path: src/bundle/EventListener/CsrfListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener\\:\\:getSubscribedEvents\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/bundle/EventListener/CsrfListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener\\:\\:onKernelRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/EventListener/CsrfListener.php - - - - message: "#^Parameter \\#1 \\$id of class Symfony\\\\Component\\\\Security\\\\Csrf\\\\CsrfToken constructor expects string, bool given\\.$#" - count: 1 - path: src/bundle/EventListener/CsrfListener.php - - - - message: "#^Property Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener\\:\\:\\$csrfTokenIntention \\(bool\\) does not accept string\\.$#" - count: 1 - path: src/bundle/EventListener/CsrfListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\RequestListener\\:\\:getSubscribedEvents\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/bundle/EventListener/RequestListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\RequestListener\\:\\:hasRestPrefix\\(\\) should return bool but returns int\\|false\\.$#" - count: 1 - path: src/bundle/EventListener/RequestListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListener\\:\\:getSubscribedEvents\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/bundle/EventListener/ResponseListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListener\\:\\:onKernelExceptionView\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/EventListener/ResponseListener.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListener\\:\\:onKernelResultView\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/EventListener/ResponseListener.php - - message: "#^Property Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\UserCheckRequestListener\\:\\:\\$permissionResolver is never read, only written\\.$#" count: 1 @@ -596,7 +546,7 @@ parameters: path: src/lib/Input/Dispatcher.php - - message: "#^Cannot access property \\$fieldTypeIdentifier on Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\ContentType\\\\FieldDefinition\\|null\\.$#" + message: "#^Cannot call method getFieldTypeIdentifier\\(\\) on Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\ContentType\\\\FieldDefinition\\|null\\.$#" count: 1 path: src/lib/Input/FieldTypeParser.php @@ -1700,76 +1650,6 @@ parameters: count: 1 path: src/lib/Server/Controller/Services.php - - - message: "#^Cannot call method authenticate\\(\\) on Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AuthenticatorInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Cannot call method getAPIUser\\(\\) on Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Cannot call method logout\\(\\) on Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\Authentication\\\\AuthenticatorInterface\\|null\\.$#" - count: 6 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:__construct\\(\\) has parameter \\$tokenIntention with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:checkCsrfToken\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:deleteSessionAction\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\DeletedUserSession but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:getAuthenticator\\(\\) never returns null so it can be removed from the return type\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:refreshSessionAction\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserSession but returns Symfony\\\\Component\\\\HttpFoundation\\\\Response\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^PHPDoc tag @var has invalid value \\(\\$session \\\\Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\Session\\)\\: Unexpected token \"\\$session\", expected type at offset 9$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^PHPDoc tag @var has invalid value \\(\\$sessionInput \\\\Ibexa\\\\Rest\\\\Server\\\\Values\\\\SessionInput\\)\\: Unexpected token \"\\$sessionInput\", expected type at offset 9$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Parameter \\#4 \\$csrfToken of class Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserSession constructor expects string, string\\|null given\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:\\$csrfTokenManager \\(Ibexa\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManager\\) does not accept Ibexa\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManager\\|null\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:\\$csrfTokenStorage \\(Symfony\\\\Component\\\\Security\\\\Csrf\\\\TokenStorage\\\\TokenStorageInterface\\) does not accept Symfony\\\\Component\\\\Security\\\\Csrf\\\\TokenStorage\\\\TokenStorageInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Controller\\\\SessionController\\:\\:\\$csrfTokenStorage is never read, only written\\.$#" - count: 1 - path: src/lib/Server/Controller/SessionController.php - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\Trash\\:\\:deleteTrashItem\\(\\) has parameter \\$trashItemId with no type specified\\.$#" count: 1 @@ -1851,137 +1731,17 @@ parameters: path: src/lib/Server/Controller/URLWildcard.php - - message: "#^Dead catch \\- Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Exceptions\\\\NotFoundException is never thrown in the try block\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Expression \"\\$this\\-\\>loadUsers\\(\\$request\\)\\-\\>users\" on a separate line does not do anything\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:assignUserToUserGroup\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:createUser\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:createUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:deleteUser\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:deleteUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadSubUserGroups\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadSubUserGroups\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserGroupList\\|Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserGroupRefList but returns Ibexa\\\\Rest\\\\Server\\\\Values\\\\CachedValue\\.$#" - count: 2 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUser\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUser\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\RestUser but returns Ibexa\\\\Rest\\\\Server\\\\Values\\\\CachedValue\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUserDrafts\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUserGroup\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\RestUserGroup but returns Ibexa\\\\Rest\\\\Server\\\\Values\\\\CachedValue\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUserGroupsOfUser\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUserGroupsOfUser\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserGroupRefList but returns Ibexa\\\\Rest\\\\Server\\\\Values\\\\CachedValue\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUsersFromGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:loadUsersFromGroup\\(\\) should return Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserList\\|Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserRefList but returns Ibexa\\\\Rest\\\\Server\\\\Values\\\\CachedValue\\.$#" - count: 2 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:moveUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:setSessionController\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:setTokenStorage\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:unassignUserFromUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:unassignUserFromUserGroup\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:updateUser\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:updateUserGroup\\(\\) has parameter \\$groupPath with no type specified\\.$#" + message: "#^Access to an undefined property Ibexa\\\\Rest\\\\Value\\:\\:\\$users\\.$#" count: 1 path: src/lib/Server/Controller/User.php - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:verifyUsers\\(\\) has no return type specified\\.$#" + message: "#^Dead catch \\- Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Exceptions\\\\NotFoundException is never thrown in the try block\\.$#" count: 1 path: src/lib/Server/Controller/User.php - - message: "#^PHPDoc tag @throws with type Ibexa\\\\Core\\\\Base\\\\Exceptions\\\\UnauthorizedException\\|Ibexa\\\\Rest\\\\Server\\\\Controller\\\\RestNotFoundException is not subtype of Throwable$#" + message: "#^Expression \"\\$this\\-\\>loadUsers\\(\\$request\\)\\-\\>users\" on a separate line does not do anything\\.$#" count: 1 path: src/lib/Server/Controller/User.php @@ -2000,16 +1760,6 @@ parameters: count: 14 path: src/lib/Server/Controller/User.php - - - message: "#^Parameter \\#1 \\$locationId of method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\LocationService\\:\\:loadLocation\\(\\) expects int, string given\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - - - message: "#^Parameter \\#1 \\$message of function trigger_error expects string, int given\\.$#" - count: 4 - path: src/lib/Server/Controller/User.php - - message: "#^Parameter \\#1 \\$path of method Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:extractLocationIdFromPath\\(\\) expects string, string\\|null given\\.$#" count: 1 @@ -2035,11 +1785,6 @@ parameters: count: 10 path: src/lib/Server/Controller/User.php - - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Controller\\\\User\\:\\:\\$csrfTokenStorage is never read, only written\\.$#" - count: 1 - path: src/lib/Server/Controller/User.php - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\Aggregation\\\\AbstractRangeAggregationParser\\:\\:dispatchRanges\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" count: 1 @@ -3605,71 +3350,6 @@ parameters: count: 1 path: src/lib/Server/Output/ValueObjectVisitor/VersionTranslationInfo.php - - - message: "#^Call to an undefined method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Security\\\\UserInterface\\:\\:isEqualTo\\(\\)\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Cannot call method getUser\\(\\) on Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestAuthenticator\\:\\:__construct\\(\\) has parameter \\$providerKey with no type specified\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestAuthenticator\\:\\:__invoke\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestAuthenticator\\:\\:addLogoutHandler\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestAuthenticator\\:\\:authenticate\\(\\) should return Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface but returns Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Parameter \\#1 \\$object of function get_class expects object, Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface\\|null given\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Parameter \\#1 \\$token of method Symfony\\\\Component\\\\Security\\\\Core\\\\Exception\\\\AuthenticationException\\:\\:setToken\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface, Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null given\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects array\\, string given\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Parameter \\#3 \\$token of method Symfony\\\\Component\\\\Security\\\\Http\\\\Logout\\\\LogoutHandlerInterface\\:\\:logout\\(\\) expects Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface, Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null given\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestAuthenticator\\:\\:\\$logger \\(Psr\\\\Log\\\\LoggerInterface\\) does not accept Psr\\\\Log\\\\LoggerInterface\\|null\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Ternary operator condition is always true\\.$#" - count: 1 - path: src/lib/Server/Security/RestAuthenticator.php - - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestLogoutHandler\\:\\:logout\\(\\) has no return type specified\\.$#" - count: 1 - path: src/lib/Server/Security/RestLogoutHandler.php - - message: "#^PHPDoc tag @return with type array\\|Ibexa\\\\Rest\\\\Values\\\\Root is not subtype of native type Ibexa\\\\Rest\\\\Values\\\\Root\\.$#" count: 1 @@ -3780,11 +3460,6 @@ parameters: count: 1 path: src/lib/Server/Values/SeeOther.php - - - message: "#^Method Ibexa\\\\Rest\\\\Server\\\\Values\\\\UserSession\\:\\:__construct\\(\\) has parameter \\$created with no type specified\\.$#" - count: 1 - path: src/lib/Server/Values/UserSession.php - - message: "#^Property Ibexa\\\\Rest\\\\Server\\\\Values\\\\Version\\:\\:\\$path \\(string\\) does not accept string\\|null\\.$#" count: 1 @@ -3911,370 +3586,10 @@ parameters: path: tests/bundle/DependencyInjection/Compiler/ValueObjectVisitorPassTest.php - - message: "#^Call to an undefined method Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\:\\:expects\\(\\)\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getCsrfProviderMock\\(\\) has invalid return type Symfony\\\\Component\\\\Form\\\\Extension\\\\Csrf\\\\CsrfProvider\\\\CsrfProviderInterface\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getCsrfProviderMock\\(\\) should return PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Form\\\\Extension\\\\Csrf\\\\CsrfProvider\\\\CsrfProviderInterface but returns PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Security\\\\Csrf\\\\CsrfTokenManagerInterface\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getEvent\\(\\) has parameter \\$class with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getEvent\\(\\) should return PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\RequestEvent but returns Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getEventDispatcherMock\\(\\) should return PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface but returns Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getIgnoredRequestMethods\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:getSessionMock\\(\\) should return PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface but returns Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:provideExpectedSubscribedEventTypes\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:provideSessionRoutes\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testCsrfDisabled\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testIgnoredRequestMethods\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testInvalidToken\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testIsNotRestRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testNoHeader\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testNoSessionStarted\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testSessionRequests\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testSessionRequests\\(\\) has parameter \\$route with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:testValidToken\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Parameter \\#4 \\$csrfTokenManager of class Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener constructor expects Symfony\\\\Component\\\\Security\\\\Csrf\\\\CsrfTokenManagerInterface\\|null, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\Form\\\\Extension\\\\Csrf\\\\CsrfProvider\\\\CsrfProviderInterface given\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$csrfTokenHeaderValue has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$eventDispatcherMock \\(Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$requestMethod has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$route \\(string\\) does not accept false\\.$#" - count: 4 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$sessionIsStarted has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$sessionMock \\(Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\) does not accept false\\.$#" - count: 2 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListenerTest\\:\\:\\$sessionMock \\(Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$event \\(Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestHeadersMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\ParameterBag\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\Request\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/CsrfListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:getEvent\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:getEvent\\(\\) has parameter \\$class with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:provideExpectedSubscribedEventTypes\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:testGetSubscribedEvents\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:testGetSubscribedEvents\\(\\) has parameter \\$expectedEventTypes with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^PHPDoc tag @param references unknown parameter\\: \\$csrfEnabled$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^PHPDoc tag @return has invalid value \\(\\\\PHPUnit\\\\Framework\\\\MockObject\\\\MockObject\\|\\$class\\)\\: Unexpected token \"\\$class\", expected type at offset 60$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$enableCsrfProtection has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$event \\(Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\) does not accept PHPUnit\\\\Framework\\\\MockObject\\\\MockObject\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$event \\(Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$isRestRequest has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestAttributesMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\ParameterBag\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestHeadersMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\ParameterBag\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestMethod has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\Request\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$requestType has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Property Symfony\\\\Component\\\\HttpFoundation\\\\Request\\:\\:\\$headers \\(Symfony\\\\Component\\\\HttpFoundation\\\\HeaderBag\\) does not accept PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpFoundation\\\\ParameterBag\\.$#" + message: "#^Parameter \\#2 \\$method of function method_exists expects string, array\\\\|int\\|string given\\.$#" count: 1 path: tests/bundle/EventListener/EventListenerTest.php - - - message: "#^Unable to resolve the template type T in call to method PHPUnit\\\\Framework\\\\TestCase\\:\\:getMockBuilder\\(\\)$#" - count: 1 - path: tests/bundle/EventListener/EventListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\RequestListenerTest\\:\\:getDataForTestOnKernelRequest\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/bundle/EventListener/RequestListenerTest.php - - - - message: "#^Return type \\(Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\RequestListener\\) of method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\RequestListenerTest\\:\\:getEventListener\\(\\) should be compatible with return type \\(Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener\\) of method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:getEventListener\\(\\)$#" - count: 1 - path: tests/bundle/EventListener/RequestListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:getControllerResultEvent\\(\\) should return Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\ViewEvent but returns Symfony\\\\Contracts\\\\EventDispatcher\\\\Event\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:getExceptionEvent\\(\\) should return Symfony\\\\Component\\\\HttpKernel\\\\Event\\\\ExceptionEvent but returns Symfony\\\\Contracts\\\\EventDispatcher\\\\Event\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelView\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelView\\(\\) has parameter \\$event with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelView\\(\\) has parameter \\$method with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelView\\(\\) has parameter \\$value with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelViewIsNotRestRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:onKernelViewIsNotRestRequest\\(\\) has parameter \\$method with no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:provideExpectedSubscribedEventTypes\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:testOnControllerResultView\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:testOnKernelExceptionView\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:testOnKernelExceptionViewIsNotRestRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:testOnKernelResultViewIsNotRestRequest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^PHPDoc type Symfony\\\\Contracts\\\\EventDispatcher\\\\Event of property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$event is not covariant with PHPDoc type Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface of overridden property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:\\$event\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$controllerResult has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$dispatcherMessage has no type specified\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$event \\(Symfony\\\\Contracts\\\\EventDispatcher\\\\Event\\) in isset\\(\\) is not nullable\\.$#" - count: 2 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$kernelMock \\(PHPUnit\\\\Framework\\\\MockObject\\\\MockObject&Symfony\\\\Component\\\\HttpKernel\\\\KernelInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Property Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:\\$visitorDispatcherMock \\(Ibexa\\\\Rest\\\\Server\\\\View\\\\AcceptHeaderVisitorDispatcher&PHPUnit\\\\Framework\\\\MockObject\\\\MockObject\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Return type \\(Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListener\\) of method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\ResponseListenerTest\\:\\:getEventListener\\(\\) should be compatible with return type \\(Ibexa\\\\Bundle\\\\Rest\\\\EventListener\\\\CsrfListener\\) of method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\EventListener\\\\EventListenerTest\\:\\:getEventListener\\(\\)$#" - count: 1 - path: tests/bundle/EventListener/ResponseListenerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\BinaryContentTest\\:\\:createContentTypeWithImageAsset\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/BinaryContentTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\BinaryContentTest\\:\\:testGetImageAssetVariations\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/BinaryContentTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\BinaryContentTest\\:\\:testGetImageVariation\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/BinaryContentTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\ContentTest\\:\\:assertTranslationDoesNotExist\\(\\) has parameter \\$versionItem with no value type specified in iterable type array\\.$#" count: 1 @@ -5813,90 +5128,35 @@ parameters: - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testLoadSectionByIdentifier\\(\\) has no return type specified\\.$#" count: 1 - path: tests/bundle/Functional/SectionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testLoadSectionByIdentifier\\(\\) has parameter \\$sectionHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SectionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testUpdateSection\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SectionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testUpdateSection\\(\\) has parameter \\$sectionHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SectionTest.php - - - - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Cannot access property \\$nodeValue on DOMNode\\|null\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:assertHttpResponseDeletesSessionCookie\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:assertHttpResponseDeletesSessionCookie\\(\\) has parameter \\$session with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testCreateSessionBadCredentials\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testDeleteSession\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testDeleteSessionExpired\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/SessionTest.php + path: tests/bundle/Functional/SectionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testDeleteSessionExpired\\(\\) has parameter \\$session with no type specified\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testLoadSectionByIdentifier\\(\\) has parameter \\$sectionHref with no type specified\\.$#" count: 1 - path: tests/bundle/Functional/SessionTest.php + path: tests/bundle/Functional/SectionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testDeleteSessionMissingCsrfToken\\(\\) has no return type specified\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testUpdateSection\\(\\) has no return type specified\\.$#" count: 1 - path: tests/bundle/Functional/SessionTest.php + path: tests/bundle/Functional/SectionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testLoginWithExistingFrontendSession\\(\\) has no return type specified\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SectionTest\\:\\:testUpdateSection\\(\\) has parameter \\$sectionHref with no type specified\\.$#" count: 1 - path: tests/bundle/Functional/SessionTest.php + path: tests/bundle/Functional/SectionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testRefreshSession\\(\\) has no return type specified\\.$#" + message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" count: 1 path: tests/bundle/Functional/SessionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testRefreshSessionExpired\\(\\) has no return type specified\\.$#" + message: "#^Cannot access property \\$nodeValue on DOMNode\\|null\\.$#" count: 1 path: tests/bundle/Functional/SessionTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\SessionTest\\:\\:testRefreshSessionMissingCsrfToken\\(\\) has no return type specified\\.$#" + message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" count: 1 path: tests/bundle/Functional/SessionTest.php @@ -6225,196 +5485,6 @@ parameters: count: 1 path: tests/bundle/Functional/UrlWildcardTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:loadRootUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testAssignUserToUserGroup\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testCreateUser\\(\\) has parameter \\$userGroupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteSession\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteSession\\(\\) has parameter \\$sessionHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteUser\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testDeleteUserGroup\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testFilterUsersByEmailQueryParameter\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testFilterUsersByLoginQueryParameter\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testIfEmailIsUsedByAnotherUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testIfLoginIsUsedByAnotherUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadSubUserGroups\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadSubUserGroups\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUser\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserByRemoteId\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserDrafts\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserDrafts\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroup\\(\\) has parameter \\$groupId with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroupByRemoteId\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroupByRemoteId\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroups\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroupsOfUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUserGroupsOfUser\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUsers\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUsersFromGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testLoadUsersFromGroup\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testMoveUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testMoveUserGroup\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUnassignUserFromUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUnassignUserFromUserGroup\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUpdateUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUpdateUser\\(\\) has parameter \\$userHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUpdateUserGroup\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Bundle\\\\Rest\\\\Functional\\\\UserTest\\:\\:testUpdateUserGroup\\(\\) has parameter \\$groupHref with no type specified\\.$#" - count: 1 - path: tests/bundle/Functional/UserTest.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 2 @@ -6490,11 +5560,6 @@ parameters: count: 1 path: tests/integration/BasicKernelTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Rest\\\\UriParser\\\\UriParserTest\\:\\:getDataForTestIsRestRequest\\(\\) return type has no value type specified in iterable type iterable\\.$#" - count: 1 - path: tests/integration/UriParser/UriParserTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\FieldTypeProcessor\\\\AuthorProcessorTest\\:\\:fieldSettingsHashes\\(\\) has no return type specified\\.$#" count: 1 @@ -6545,11 +5610,6 @@ parameters: count: 1 path: tests/lib/FieldTypeProcessor/BinaryInputProcessorTest.php - - - message: "#^Parameter \\#1 \\$filename of function unlink expects string, string\\|false given\\.$#" - count: 1 - path: tests/lib/FieldTypeProcessor/BinaryInputProcessorTest.php - - message: "#^Property Ibexa\\\\Tests\\\\Rest\\\\FieldTypeProcessor\\\\BinaryInputProcessorTest\\:\\:\\$tempDir has no type specified\\.$#" count: 1 @@ -8130,31 +7190,6 @@ parameters: count: 1 path: tests/lib/Server/Input/Parser/FieldDefinitionCreateTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\FieldDefinitionUpdateTest\\:\\:getInputArray\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\FieldDefinitionUpdateTest\\:\\:getParseHrefExpectationsMap\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\FieldDefinitionUpdateTest\\:\\:testParse\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\FieldDefinitionUpdateTest\\:\\:testParseExceptionOnInvalidDescriptions\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\FieldDefinitionUpdateTest\\:\\:testParseExceptionOnInvalidNames\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Input\\\\Parser\\\\Limitation\\\\PathStringRouteBasedLimitationParserTest\\:\\:getParseHrefExpectationsMap\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -10670,121 +9705,6 @@ parameters: count: 1 path: tests/lib/Server/Output/ValueObjectVisitor/VersionTest.php - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManagerTest\\:\\:createCsrfTokenManager\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/CsrfTokenManagerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManagerTest\\:\\:createCsrfTokenManager\\(\\) has parameter \\$https with no type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/CsrfTokenManagerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManagerTest\\:\\:testHasTokenForHttp\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/CsrfTokenManagerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\CsrfTokenManagerTest\\:\\:testHasTokenForHttps\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/CsrfTokenManagerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestLogoutHandlerTest\\:\\:testLogoutNotRest\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestLogoutHandlerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestLogoutHandlerTest\\:\\:testLogoutWithSiteaccessSessionSettings\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestLogoutHandlerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestLogoutHandlerTest\\:\\:testLogoutWithoutSiteaccessSessionSettings\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestLogoutHandlerTest.php - - - - message: "#^Parameter \\#1 \\$configResolver of class Ibexa\\\\Rest\\\\Server\\\\Security\\\\RestLogoutHandler constructor expects Ibexa\\\\Contracts\\\\Core\\\\SiteAccess\\\\ConfigResolverInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 3 - path: tests/lib/Server/Security/RestLogoutHandlerTest.php - - - - message: "#^Parameter \\#1 \\$session of method Symfony\\\\Component\\\\HttpFoundation\\\\Request\\:\\:setSession\\(\\) expects Symfony\\\\Component\\\\HttpFoundation\\\\Session\\\\SessionInterface, PHPUnit\\\\Framework\\\\MockObject\\\\MockObject given\\.$#" - count: 2 - path: tests/lib/Server/Security/RestLogoutHandlerTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:createUser\\(\\) has parameter \\$userId with no type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:getTokenInterfaceMock\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:getUsernamePasswordTokenMock\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticate\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticateAlreadyHaveSessionToken\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticateInvalidUser\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticateNoTokenFound\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticatePreviousTokenNotUsernamePassword\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticatePreviousUserNonEz\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticatePreviouslyAnonymous\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testAuthenticateUserConflict\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\Server\\\\Security\\\\RestSessionBasedAuthenticatorTest\\:\\:testLogout\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Parameter \\#1 \\$user of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\UserInterface, string given\\.$#" - count: 7 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - - - message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects array\\, string given\\.$#" - count: 7 - path: tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Rest\\\\UrlHandler\\\\PatternTest\\:\\:getParseValues\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/src/bundle/DependencyInjection/Security/RestSessionBasedFactory.php b/src/bundle/DependencyInjection/Security/RestSessionBasedFactory.php deleted file mode 100644 index 01486c4f..00000000 --- a/src/bundle/DependencyInjection/Security/RestSessionBasedFactory.php +++ /dev/null @@ -1,109 +0,0 @@ -options['check_path']); - - $this->defaultSuccessHandlerOptions = []; - $this->defaultFailureHandlerOptions = []; - } - - /** - * @param array $config - */ - protected function isRememberMeAware(array $config): bool - { - return false; - } - - /** - * @param array $config - */ - protected function createListener( - ContainerBuilder $container, - string $id, - array $config, - ?string $userProvider - ): string { - $listenerId = $this->getListenerId(); - $listener = new ChildDefinition($listenerId); - $listener->replaceArgument(2, $id); - - /* @var \Symfony\Component\DependencyInjection\ContainerBuilder $container */ - $listenerId .= '.' . $id; - $container->setDefinition($listenerId, $listener); - $container->setAlias('ibexa.rest.session_authenticator', $listenerId); - - if ($container->hasDefinition('security.logout_listener.' . $id)) { - // Copying logout handlers to REST session authenticator, to allow proper logout using it. - $logoutListenerDef = $container->getDefinition('security.logout_listener.' . $id); - $logoutListenerDef->addMethodCall( - 'addHandler', - [new Reference(RestLogoutHandler::class)] - ); - - foreach ($logoutListenerDef->getMethodCalls() as $callArray) { - if ($callArray[0] !== 'addHandler') { - continue; - } - - $listener->addMethodCall('addLogoutHandler', $callArray[1]); - } - } - - return $listenerId; - } - - protected function getListenerId(): string - { - return 'ibexa.rest.security.authentication.listener.session'; - } - - public function getPosition(): string - { - return 'http'; - } - - public function getKey(): string - { - return 'ibexa_rest_session'; - } - - /** - * @param array $config - */ - protected function createEntryPoint( - ContainerBuilder $container, - string $id, - array $config, - ?string $defaultEntryPointId - ): ?string { - return $defaultEntryPointId; - } - - public function createAuthenticator( - ContainerBuilder $container, - string $firewallName, - array $config, - string $userProviderId - ): string { - return parent::createAuthenticator($container, $firewallName . '__rest', $config, $userProviderId); - } -} diff --git a/src/bundle/EventListener/CsrfListener.php b/src/bundle/EventListener/CsrfListener.php index 80d4ee57..81df858b 100644 --- a/src/bundle/EventListener/CsrfListener.php +++ b/src/bundle/EventListener/CsrfListener.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Bundle\Rest\EventListener; @@ -17,47 +18,30 @@ use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -class CsrfListener implements EventSubscriberInterface +final class CsrfListener implements EventSubscriberInterface { /** * Name of the HTTP header containing CSRF token. */ - public const CSRF_TOKEN_HEADER = 'X-CSRF-Token'; + public const string CSRF_TOKEN_HEADER = 'X-CSRF-Token'; - /** - * @var \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface|null - */ - private $csrfTokenManager; + private ?CsrfTokenManagerInterface $csrfTokenManager; - /** - * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface - */ - private $eventDispatcher; + private EventDispatcherInterface $eventDispatcher; - /** - * @var bool - */ - private $csrfEnabled; + private bool $csrfEnabled; - /** - * @var bool - */ - private $csrfTokenIntention; + private string $csrfTokenIntention; /** * Note that CSRF provider needs to be optional as it will not be available * when CSRF protection is disabled. - * - * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher - * @param bool $csrfEnabled - * @param string $csrfTokenIntention - * @param \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface|null $csrfTokenManager */ public function __construct( EventDispatcherInterface $eventDispatcher, - $csrfEnabled, - $csrfTokenIntention, - CsrfTokenManagerInterface $csrfTokenManager = null + bool $csrfEnabled, + string $csrfTokenIntention, + ?CsrfTokenManagerInterface $csrfTokenManager = null ) { $this->eventDispatcher = $eventDispatcher; $this->csrfEnabled = $csrfEnabled; @@ -65,10 +49,7 @@ public function __construct( $this->csrfTokenManager = $csrfTokenManager; } - /** - * @return array - */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => 'onKernelRequest', @@ -78,13 +59,12 @@ public static function getSubscribedEvents() /** * This method validates CSRF token if CSRF protection is enabled. * - * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event - * * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function onKernelRequest(RequestEvent $event) + public function onKernelRequest(RequestEvent $event): void { - if (!$event->getRequest()->attributes->get('is_rest_request')) { + $request = $event->getRequest(); + if (!$request->attributes->get('is_rest_request')) { return; } @@ -93,26 +73,22 @@ public function onKernelRequest(RequestEvent $event) } // skip CSRF validation if no session is running - if (!$event->getRequest()->getSession()->isStarted()) { + if (!$request->getSession()->isStarted()) { return; } - if ($this->isMethodSafe($event->getRequest()->getMethod())) { + if ($this->isMethodSafe($request->getMethod())) { return; } - if ($this->isSessionRoute($event->getRequest()->get('_route'))) { + if (!$request->attributes->getBoolean('csrf_protection', true)) { return; } - if (!$event->getRequest()->attributes->getBoolean('csrf_protection', true)) { - return; - } - - if (!$this->checkCsrfToken($event->getRequest())) { + if (!$this->checkCsrfToken($request)) { throw new UnauthorizedException( 'Missing or invalid CSRF token', - $event->getRequest()->getMethod() . ' ' . $event->getRequest()->getPathInfo() + $request->getMethod() . ' ' . $request->getPathInfo() ); } @@ -120,58 +96,21 @@ public function onKernelRequest(RequestEvent $event) $this->eventDispatcher->dispatch($event, RestEvents::REST_CSRF_TOKEN_VALIDATED); } - /** - * @param string $method - * - * @return bool - */ - protected function isMethodSafe($method) + private function isMethodSafe(string $method): bool { return in_array($method, ['GET', 'HEAD', 'OPTIONS']); } - /** - * @param string $route - * - * @return bool - * - * @deprecated Deprecated since 6.5. Use isSessionRoute() instead. - */ - protected function isLoginRequest($route) - { - return $route === 'ibexa.rest.create_session'; - } - - /** - * Tests if a given $route is a session management one. - * - * @param string $route - * - * @return bool - * - * @deprecated since Ibexa DXP 3.3.7. Add csrf_protection: false attribute to route definition instead. - */ - protected function isSessionRoute($route) - { - return in_array( - $route, - ['ibexa.rest.create_session', 'ibexa.rest.refresh_session', 'ibexa.rest.delete_session'] - ); - } - - /** - * Checks the validity of the request's csrf token header. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * - * @return bool true/false if the token is valid/invalid, false if none was found in the request's headers - */ - protected function checkCsrfToken(Request $request) + private function checkCsrfToken(Request $request): bool { if (!$request->headers->has(self::CSRF_TOKEN_HEADER)) { return false; } + if ($this->csrfTokenManager === null) { + return false; + } + return $this->csrfTokenManager->isTokenValid( new CsrfToken( $this->csrfTokenIntention, diff --git a/src/bundle/EventListener/RequestListener.php b/src/bundle/EventListener/RequestListener.php index 76011d41..8fa22f1d 100644 --- a/src/bundle/EventListener/RequestListener.php +++ b/src/bundle/EventListener/RequestListener.php @@ -7,10 +7,8 @@ namespace Ibexa\Bundle\Rest\EventListener; -use Ibexa\Bundle\Rest\UriParser\UriParser; use Ibexa\Contracts\Rest\UriParser\UriParserInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -21,14 +19,8 @@ * * Flags a REST request as such using the is_rest_request attribute. */ -class RequestListener implements EventSubscriberInterface +final class RequestListener implements EventSubscriberInterface { - /** - * @deprecated rely on \Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest instead. - * @see \Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest() - */ - public const REST_PREFIX_PATTERN = UriParser::DEFAULT_REST_PREFIX_PATTERN; - private UriParserInterface $uriParser; public function __construct(UriParserInterface $uriParser) @@ -36,9 +28,6 @@ public function __construct(UriParserInterface $uriParser) $this->uriParser = $uriParser; } - /** - * @return array - */ public static function getSubscribedEvents(): array { return [ @@ -57,17 +46,4 @@ public function onKernelRequest(RequestEvent $event): void $this->uriParser->isRestRequest($event->getRequest()) ); } - - /** - * @param \Symfony\Component\HttpFoundation\Request $request - * - * @return bool - * - * @deprecated use \Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest instead - * @see \Ibexa\Contracts\Rest\UriParser\UriParserInterface::isRestRequest() - */ - protected function hasRestPrefix(Request $request) - { - return preg_match(self::REST_PREFIX_PATTERN, $request->getPathInfo()); - } } diff --git a/src/bundle/EventListener/ResponseListener.php b/src/bundle/EventListener/ResponseListener.php index 3bf9a81f..aeacde42 100644 --- a/src/bundle/EventListener/ResponseListener.php +++ b/src/bundle/EventListener/ResponseListener.php @@ -23,27 +23,18 @@ * * Converts responses from REST controllers to REST Responses, depending on the Accept-Header value. */ -class ResponseListener implements EventSubscriberInterface, LoggerAwareInterface +final class ResponseListener implements EventSubscriberInterface, LoggerAwareInterface { use LoggerAwareTrait; - /** - * @var \Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher - */ - private $viewDispatcher; + private AcceptHeaderVisitorDispatcher $viewDispatcher; - /** - * @param $viewDispatcher AcceptHeaderVisitorDispatcher - */ public function __construct(AcceptHeaderVisitorDispatcher $viewDispatcher) { $this->viewDispatcher = $viewDispatcher; } - /** - * @return array - */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::VIEW => 'onKernelResultView', @@ -52,10 +43,7 @@ public static function getSubscribedEvents() ]; } - /** - * @param \Symfony\Component\HttpKernel\Event\ViewEvent $event - */ - public function onKernelResultView(ViewEvent $event) + public function onKernelResultView(ViewEvent $event): void { if (!$event->getRequest()->attributes->get('is_rest_request')) { return; @@ -71,11 +59,9 @@ public function onKernelResultView(ViewEvent $event) } /** - * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event - * * @throws \Exception */ - public function onKernelExceptionView(ExceptionEvent $event) + public function onKernelExceptionView(ExceptionEvent $event): void { if (!$event->getRequest()->attributes->get('is_rest_request')) { return; diff --git a/src/bundle/IbexaRestBundle.php b/src/bundle/IbexaRestBundle.php index 5e1cf4b2..0bdbb1d7 100644 --- a/src/bundle/IbexaRestBundle.php +++ b/src/bundle/IbexaRestBundle.php @@ -9,7 +9,6 @@ namespace Ibexa\Bundle\Rest; use Ibexa\Bundle\Rest\DependencyInjection\Compiler; -use Ibexa\Bundle\Rest\DependencyInjection\Security\RestSessionBasedFactory; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -25,10 +24,6 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new Compiler\OutputVisitorPass()); $container->addCompilerPass(new Compiler\ValueObjectVisitorPass()); - /** @var \Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension $securityExtension */ - $securityExtension = $container->getExtension('security'); - $securityExtension->addAuthenticatorFactory(new RestSessionBasedFactory()); - if ($container->hasExtension('lexik_jwt_authentication')) { $container->addCompilerPass(new Compiler\LexikAuthorizationHeaderBridgePass()); } diff --git a/src/bundle/Resources/config/routing.yml b/src/bundle/Resources/config/routing.yml index b2ac808c..629b8cd0 100644 --- a/src/bundle/Resources/config/routing.yml +++ b/src/bundle/Resources/config/routing.yml @@ -47,6 +47,12 @@ ibexa.rest.delete_section: requirements: sectionId: \d+ +ibexa.rest.refresh_session: + path: /user/sessions/{sessionId}/refresh + defaults: + _controller: Ibexa\Rest\Server\Controller\SessionController:refreshSessionAction + csrf_protection: false + methods: [POST] # Content @@ -1160,14 +1166,6 @@ ibexa.rest.delete_session: csrf_protection: false methods: [DELETE] -ibexa.rest.refresh_session: - path: /user/sessions/{sessionId}/refresh - defaults: - _controller: Ibexa\Rest\Server\Controller\SessionController:refreshSessionAction - csrf_protection: false - methods: [POST] - - # URL aliases diff --git a/src/bundle/Resources/config/security.yml b/src/bundle/Resources/config/security.yml index 1b37a0a8..61c75b76 100644 --- a/src/bundle/Resources/config/security.yml +++ b/src/bundle/Resources/config/security.yml @@ -2,26 +2,15 @@ parameters: ibexa.rest.authorization_header_name: ~ services: - # Following service will be aliased at compile time to "ezpublish_rest.session_authenticator" by the Security factory. - ibexa.rest.security.authentication.listener.session: - class: Ibexa\Rest\Server\Security\RestAuthenticator - arguments: - - "@security.token_storage" - - "@security.authentication.manager" - - ~ # Will be replaced at compile time by security provider key - - "@event_dispatcher" - - '@ibexa.config.resolver' - - "@?logger" - abstract: true + _defaults: + autowire: true + autoconfigure: false + public: false Ibexa\Contracts\Rest\Security\AuthorizationHeaderRESTRequestMatcher: arguments: $headerName: '%ibexa.rest.authorization_header_name%' - Ibexa\Rest\Server\Security\RestLogoutHandler: - arguments: - - '@ibexa.config.resolver' - Ibexa\Rest\Server\Security\CsrfTokenManager: arguments: - '@?security.csrf.token_generator' @@ -29,7 +18,7 @@ services: - '@?request_stack' Ibexa\Rest\Server\Security\EventListener\SecurityListener: - arguments: - - '@Ibexa\Contracts\Core\Repository\PermissionResolver' tags: - { name: kernel.event_subscriber } + + Ibexa\Rest\Security\Authenticator\RestAuthenticator: ~ diff --git a/src/bundle/Resources/config/services.yml b/src/bundle/Resources/config/services.yml index 56b7b449..7bfedb25 100644 --- a/src/bundle/Resources/config/services.yml +++ b/src/bundle/Resources/config/services.yml @@ -179,11 +179,12 @@ services: class: Ibexa\Rest\Server\Controller\SessionController parent: Ibexa\Rest\Server\Controller arguments: - - '%ibexa.rest.csrf_token_intention%' - - '@Ibexa\Contracts\Core\Repository\PermissionResolver' - - '@ibexa.api.service.user' - - '@?ibexa.rest.session_authenticator' - - '@?Ibexa\Rest\Server\Security\CsrfTokenManager' + $permissionResolver: '@Ibexa\Contracts\Core\Repository\PermissionResolver' + $userService: '@Ibexa\Contracts\Core\Repository\UserService' + $csrfTokenManager: '@Ibexa\Rest\Server\Security\CsrfTokenManager' + $securityTokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + $csrfTokenIntention: '%ibexa.rest.csrf_token_intention%' + $configResolver: '@Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface' tags: [controller.service_arguments] Ibexa\Rest\Server\Controller\Bookmark: diff --git a/src/lib/Input/FieldTypeParser.php b/src/lib/Input/FieldTypeParser.php index 5043a51a..f9b62a0d 100644 --- a/src/lib/Input/FieldTypeParser.php +++ b/src/lib/Input/FieldTypeParser.php @@ -69,7 +69,7 @@ public function parseFieldValue($contentInfoId, $fieldDefIdentifier, $value) $fieldDefinition = $contentType->getFieldDefinition($fieldDefIdentifier); - return $this->parseValue($fieldDefinition->fieldTypeIdentifier, $value); + return $this->parseValue($fieldDefinition->getFieldTypeIdentifier(), $value); } /** diff --git a/src/lib/Security/Authenticator/RestAuthenticator.php b/src/lib/Security/Authenticator/RestAuthenticator.php new file mode 100644 index 00000000..9cd326d0 --- /dev/null +++ b/src/lib/Security/Authenticator/RestAuthenticator.php @@ -0,0 +1,127 @@ +attributes->get('_route') === self::LOGIN_ROUTE; + } + + public function authenticate(Request $request): Passport + { + $existingUserToken = $this->fetchExistingToken($request); + if ($this->canUserFromSessionBeAuthenticated($existingUserToken)) { + /** @phpstan-ignore-next-line */ + $existingUser = $existingUserToken->getUser(); + + return $this->createAuthorizationPassport( + /** @phpstan-ignore-next-line */ + $existingUser->getUserIdentifier(), + /** @phpstan-ignore-next-line */ + $existingUser->getPassword() + ); + } + + /** @var \Ibexa\Rest\Server\Values\SessionInput $sessionInput */ + $sessionInput = $this->inputDispatcher->parse( + new Message( + ['Content-Type' => $request->headers->get('Content-Type')], + $request->getContent() + ) + ); + + $login = $sessionInput->login; + $password = $sessionInput->password; + + $request->attributes->set('username', $login); + $request->attributes->set('password', $password); + + return $this->createAuthorizationPassport($login, $password); + } + + public function onAuthenticationSuccess( + Request $request, + TokenInterface $token, + string $firewallName + ): ?Response { + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response + { + throw $exception; + } + + public function isInteractive(): bool + { + return true; + } + + private function fetchExistingToken(Request $request): ?TokenInterface + { + // If a token already exists and username is the same as the one we request authentication for, + // then return it and mark it as coming from session. + $previousToken = $this->tokenStorage->getToken(); + if ( + $previousToken === null || + $previousToken->getUsername() !== $request->attributes->get('username') + ) { + return null; + } + + $previousToken->setAttribute('isFromSession', true); + + return $previousToken; + } + + private function canUserFromSessionBeAuthenticated(?TokenInterface $existingUserToken): bool + { + if ($existingUserToken === null) { + return false; + } + + $user = $existingUserToken->getUser(); + if ($user === null || $user->getPassword() === null) { + return false; + } + + return true; + } + + private function createAuthorizationPassport(string $login, string $password): Passport + { + return new Passport( + new UserBadge($login), + new PasswordCredentials($password), + ); + } +} diff --git a/src/lib/Server/Controller/SessionController.php b/src/lib/Server/Controller/SessionController.php index 9c39c92a..a6a0c19a 100644 --- a/src/lib/Server/Controller/SessionController.php +++ b/src/lib/Server/Controller/SessionController.php @@ -4,86 +4,57 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Controller; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Base\Exceptions\UnauthorizedException; -use Ibexa\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface; -use Ibexa\Rest\Message; use Ibexa\Rest\Server\Controller; use Ibexa\Rest\Server\Exceptions; use Ibexa\Rest\Server\Security\CsrfTokenManager; use Ibexa\Rest\Server\Values; +use Ibexa\Rest\Value as RestValue; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface as SecurityTokenStorageInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; -class SessionController extends Controller +final class SessionController extends Controller { - /** @var \Ibexa\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface|null */ - private $authenticator; - - /** @var \Ibexa\Rest\Server\Security\CsrfTokenManager */ - private $csrfTokenManager; - - /** @var string */ - private $csrfTokenIntention; - - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ - private $permissionResolver; - - /** @var \Ibexa\Contracts\Core\Repository\UserService */ - private $userService; - - /** @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface */ - private $csrfTokenStorage; - public function __construct( - $tokenIntention, - PermissionResolver $permissionResolver, - UserService $userService, - ?AuthenticatorInterface $authenticator = null, - CsrfTokenManager $csrfTokenManager = null, - TokenStorageInterface $csrfTokenStorage = null + private readonly PermissionResolver $permissionResolver, + private readonly UserService $userService, + private readonly CsrfTokenManager $csrfTokenManager, + private readonly SecurityTokenStorageInterface $securityTokenStorage, + private readonly string $csrfTokenIntention, + private readonly ConfigResolverInterface $configResolver, ) { - $this->authenticator = $authenticator; - $this->csrfTokenIntention = $tokenIntention; - $this->csrfTokenManager = $csrfTokenManager; - $this->permissionResolver = $permissionResolver; - $this->userService = $userService; - $this->csrfTokenStorage = $csrfTokenStorage; } /** - * Creates a new session based on the credentials provided as POST parameters. - * - * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF - * - * @return Values\UserSession|Values\Conflict + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function createSessionAction(Request $request) + public function createSessionAction(Request $request): RestValue { - /** @var $sessionInput \Ibexa\Rest\Server\Values\SessionInput */ - $sessionInput = $this->inputDispatcher->parse( - new Message( - ['Content-Type' => $request->headers->get('Content-Type')], - $request->getContent() - ) - ); - $request->attributes->set('username', $sessionInput->login); - $request->attributes->set('password', (string) $sessionInput->password); - try { $session = $request->getSession(); - $token = $this->getAuthenticator()->authenticate($request); $csrfToken = $this->getCsrfToken(); + $token = $this->securityTokenStorage->getToken(); + + if ($token === null) { + throw new UnauthorizedException('authorization', 'The current user is not authenticated.'); + } + + /** @var \Ibexa\Core\MVC\Symfony\Security\User $user */ + $user = $token->getUser(); return new Values\UserSession( - $token->getUser()->getAPIUser(), + $user->getAPIUser(), $session->getName(), $session->getId(), $csrfToken, @@ -93,43 +64,22 @@ public function createSessionAction(Request $request) // Already logged in with another user, this will be converted to HTTP status 409 return new Values\Conflict(); } catch (AuthenticationException $e) { - $this->getAuthenticator()->logout($request); throw new UnauthorizedException('Invalid login or password', $request->getPathInfo()); } catch (AccessDeniedException $e) { - $this->getAuthenticator()->logout($request); throw new UnauthorizedException($e->getMessage(), $request->getPathInfo()); } } /** - * Refresh given session. - * - * @deprecated 4.6.7 The "SessionController::refreshSessionAction()" method is deprecated, will be removed in 5.0. Use SessionController::checkSessionAction() instead. - * - * @param string $sessionId - * - * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException - * - * @return \Ibexa\Rest\Server\Values\UserSession + * @return \Ibexa\Rest\Server\Values\UserSession|\Symfony\Component\HttpFoundation\Response */ - public function refreshSessionAction($sessionId, Request $request) + public function checkSessionAction(Request $request) { - trigger_deprecation( - 'ibexa/rest', - '4.6.7', - sprintf('The %s() method is deprecated, will be removed in 5.0.', __METHOD__) - ); - $session = $request->getSession(); - - if ($session === null || !$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { - $response = $this->getAuthenticator()->logout($request); - $response->setStatusCode(404); - - return $response; + if ($session === null || !$session->isStarted()) { + return $this->logout($request); } - $this->checkCsrfToken($request); $currentUser = $this->userService->loadUser( $this->permissionResolver->getCurrentUserReference()->getUserId() ); @@ -138,25 +88,36 @@ public function refreshSessionAction($sessionId, Request $request) $currentUser, $session->getName(), $session->getId(), - $request->headers->get('X-CSRF-Token'), + $this->getCsrfToken(), false ); } /** + * Refresh given session. + * + * @deprecated 4.6.7 The "SessionController::refreshSessionAction()" method is deprecated, will be removed in the next API version. Use SessionController::checkSessionAction() instead. + * * @return \Ibexa\Rest\Server\Values\UserSession|\Symfony\Component\HttpFoundation\Response + * + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException */ - public function checkSessionAction(Request $request) + public function refreshSessionAction(string $sessionId, Request $request) { - $session = $request->getSession(); + trigger_deprecation( + 'ibexa/rest', + '4.6.7', + sprintf('The %s() method is deprecated, will be removed in the next API version.', __METHOD__) + ); - if ($session === null || !$session->isStarted()) { - $response = $this->getAuthenticator()->logout($request); - $response->setStatusCode(404); + $session = $request->getSession(); - return $response; + if ($session === null || !$session->isStarted() || $session->getId() !== $sessionId || !$this->hasStoredCsrfToken()) { + return $this->logout($request); } + $this->checkCsrfToken($request); $currentUser = $this->userService->loadUser( $this->permissionResolver->getCurrentUserReference()->getUserId() ); @@ -165,63 +126,43 @@ public function checkSessionAction(Request $request) $currentUser, $session->getName(), $session->getId(), - $this->getCsrfToken(), + $request->headers->get('X-CSRF-Token') ?? '', false ); } /** - * Deletes given session. - * - * @param string $sessionId + * @return \Ibexa\Rest\Server\Values\DeletedUserSession|\Symfony\Component\HttpFoundation\Response * - * @return Values\DeletedUserSession - * - * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function deleteSessionAction($sessionId, Request $request) + public function deleteSessionAction(string $sessionId, Request $request) { - /** @var $session \Symfony\Component\HttpFoundation\Session\Session */ + /** @var \Symfony\Component\HttpFoundation\Session\Session $session */ $session = $request->getSession(); - if (!$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { - $response = $this->getAuthenticator()->logout($request); - $response->setStatusCode(404); - - return $response; + if (!$session->isStarted() || $session->getId() !== $sessionId || !$this->hasStoredCsrfToken()) { + return $this->logout($request); } $this->checkCsrfToken($request); - return new Values\DeletedUserSession($this->getAuthenticator()->logout($request)); + return new Values\DeletedUserSession( + $this->logout($request) + ); } - /** - * Tests if a CSRF token is stored. - * - * @return bool - */ - private function hasStoredCsrfToken() + private function hasStoredCsrfToken(): bool { - if ($this->csrfTokenManager === null) { - return true; - } - return $this->csrfTokenManager->hasToken($this->csrfTokenIntention); } /** * Checks the presence / validity of the CSRF token. * - * @param \Symfony\Component\HttpFoundation\Request $request - * * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException if the token is missing or invalid */ - private function checkCsrfToken(Request $request) + private function checkCsrfToken(Request $request): void { - if ($this->csrfTokenManager === null) { - return; - } - if (!$request->headers->has('X-CSRF-Token')) { throw $this->createInvalidCsrfTokenException($request); } @@ -236,34 +177,11 @@ private function checkCsrfToken(Request $request) } } - /** - * Returns the csrf token for REST. The token is generated if it doesn't exist. - * - * @return string the csrf token, or an empty string if csrf check is disabled - */ - private function getCsrfToken() + private function getCsrfToken(): string { - if ($this->csrfTokenManager === null) { - return ''; - } - return $this->csrfTokenManager->getToken($this->csrfTokenIntention)->getValue(); } - private function getAuthenticator(): ?AuthenticatorInterface - { - if (null === $this->authenticator) { - throw new \RuntimeException( - sprintf( - "No %s instance injected. Ensure 'ibexa.rest.session' is configured under your firewall", - AuthenticatorInterface::class - ) - ); - } - - return $this->authenticator; - } - private function createInvalidCsrfTokenException(Request $request): UnauthorizedException { return new UnauthorizedException( @@ -271,4 +189,33 @@ private function createInvalidCsrfTokenException(Request $request): Unauthorized $request->getMethod() . ' ' . $request->getPathInfo() ); } + + private function logout(Request $request): Response + { + $path = '/'; + $domain = null; + + $session = $this->configResolver->getParameter('session'); + if (array_key_exists('cookie_domain', $session)) { + $domain = $session['cookie_domain']; + } + + if (array_key_exists('cookie_path', $session)) { + $path = $session['cookie_path']; + } + + $response = new Response(); + $requestSession = $request->getSession(); + + $response->headers->clearCookie( + $requestSession->getName(), + $path, + $domain + ); + + $response->setStatusCode(404); + $requestSession->clear(); + + return $response; + } } diff --git a/src/lib/Server/Controller/User.php b/src/lib/Server/Controller/User.php index 8da3347a..a75ca5b7 100644 --- a/src/lib/Server/Controller/User.php +++ b/src/lib/Server/Controller/User.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Controller; @@ -27,58 +28,28 @@ use Ibexa\Rest\Server\Exceptions; use Ibexa\Rest\Server\Exceptions\ForbiddenException; use Ibexa\Rest\Server\Values; +use Ibexa\Rest\Value as RestValue; use JMS\TranslationBundle\Annotation\Ignore; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; /** * User controller. */ -class User extends RestController +final class User extends RestController { - /** - * User service. - * - * @var \Ibexa\Contracts\Core\Repository\UserService - */ - protected $userService; + protected UserService $userService; - /** - * Role service. - * - * @var \Ibexa\Contracts\Core\Repository\RoleService - */ - protected $roleService; + protected RoleService $roleService; - /** - * Content service. - * - * @var \Ibexa\Contracts\Core\Repository\ContentService - */ - protected $contentService; + protected ContentService $contentService; - /** - * Content service. - * - * @var \Ibexa\Contracts\Core\Repository\ContentTypeService - */ - protected $contentTypeService; + protected ContentTypeService $contentTypeService; - /** - * Location service. - * - * @var \Ibexa\Contracts\Core\Repository\LocationService - */ - protected $locationService; + protected LocationService $locationService; - /** - * Section service. - * - * @var \Ibexa\Contracts\Core\Repository\SectionService - */ - protected $sectionService; + protected SectionService $sectionService; /** * Repository. @@ -87,22 +58,7 @@ class User extends RestController */ protected $repository; - /** - * @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface - * - * @deprecated This property is deprecated since 6.5, and will be removed in 7.0. - */ - private $csrfTokenStorage; - - /** - * @var \Ibexa\Rest\Server\Controller\SessionController - * - * @deprecated This property is added for backward compatibility. It is deprecated, and will be removed in 7.0. - */ - private $sessionController; - - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ - private $permissionResolver; + private PermissionResolver $permissionResolver; public function __construct( UserService $userService, @@ -126,10 +82,8 @@ public function __construct( /** * Redirects to the root user group. - * - * @return \Ibexa\Rest\Server\Values\PermanentRedirect */ - public function loadRootUserGroup() + public function loadRootUserGroup(): Values\PermanentRedirect { //@todo Replace hardcoded value with one loaded from settings return new Values\PermanentRedirect( @@ -139,18 +93,14 @@ public function loadRootUserGroup() /** * Loads a user group for the given path. - * - * @param $groupPath - * - * @return \Ibexa\Rest\Server\Values\RestUserGroup */ - public function loadUserGroup($groupPath) + public function loadUserGroup(string $groupPath): RestValue { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) ); - if (trim($userGroupLocation->pathString, '/') != $groupPath) { + if (trim($userGroupLocation->pathString, '/') !== $groupPath) { throw new NotFoundException( "Could not find a Location with path string $groupPath" ); @@ -175,14 +125,7 @@ public function loadUserGroup($groupPath) ); } - /** - * Loads a user for the given ID. - * - * @param $userId - * - * @return \Ibexa\Rest\Server\Values\RestUser - */ - public function loadUser($userId) + public function loadUser(int $userId): RestValue { $user = $this->userService->loadUser($userId, Language::ALL); @@ -239,13 +182,13 @@ public function redirectToCurrentUser(?UserInterface $user): Values\TemporaryRed * Create a new user group under the given parent * To create a top level group use /user/groups/1/5/subgroups. * - * @param $groupPath - * - * @throws \Ibexa\Rest\Server\Exceptions\BadRequestException - * - * @return \Ibexa\Rest\Server\Values\CreatedUserGroup + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function createUserGroup($groupPath, Request $request) + public function createUserGroup(string $groupPath, Request $request): Values\CreatedUserGroup { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -283,13 +226,12 @@ public function createUserGroup($groupPath, Request $request) /** * Create a new user group in the given group. * - * @param $groupPath - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\CreatedUser + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function createUser($groupPath, Request $request) + public function createUser(string $groupPath, Request $request): Values\CreatedUser { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -326,14 +268,7 @@ public function createUser($groupPath, Request $request) ); } - /** - * Updates a user group. - * - * @param $groupPath - * - * @return \Ibexa\Rest\Server\Values\RestUserGroup - */ - public function updateUserGroup($groupPath, Request $request) + public function updateUserGroup(string $groupPath, Request $request): Values\RestUserGroup { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -376,14 +311,7 @@ public function updateUserGroup($groupPath, Request $request) ); } - /** - * Updates a user. - * - * @param $userId - * - * @return \Ibexa\Rest\Server\Values\RestUser - */ - public function updateUser($userId, Request $request) + public function updateUser(int $userId, Request $request): Values\RestUser { $user = $this->userService->loadUser($userId); @@ -423,13 +351,10 @@ public function updateUser($userId, Request $request) /** * Given user group is deleted. * - * @param $groupPath - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\NoContent + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function deleteUserGroup($groupPath) + public function deleteUserGroup(string $groupPath): Values\NoContent { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -453,13 +378,10 @@ public function deleteUserGroup($groupPath) /** * Given user is deleted. * - * @param $userId - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\NoContent + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function deleteUser($userId) + public function deleteUser(int $userId): Values\NoContent { $user = $this->userService->loadUser($userId); @@ -474,10 +396,8 @@ public function deleteUser($userId) /** * Loads users. - * - * @return \Ibexa\Rest\Server\Values\UserList|\Ibexa\Rest\Server\Values\UserRefList */ - public function loadUsers(Request $request) + public function loadUsers(Request $request): RestValue { $restUsers = []; @@ -521,7 +441,7 @@ public function loadUsers(Request $request) return new Values\UserRefList($restUsers, $request->getPathInfo()); } - public function verifyUsers(Request $request) + public function verifyUsers(Request $request): Values\OK { // We let the NotFoundException loadUsers throws if there are no results pass. $this->loadUsers($request)->users; @@ -536,7 +456,7 @@ public function verifyUsers(Request $request) * * @return \Ibexa\Rest\Server\Values\RestUser[] */ - public function loadUsersAssignedToRole($roleId) + public function loadUsersAssignedToRole($roleId): array { $role = $this->roleService->loadRole($roleId); $roleAssignments = $this->roleService->getRoleAssignments($role); @@ -552,10 +472,7 @@ public function loadUsersAssignedToRole($roleId) return $restUsers; } - /** - * @return Values\RestUser - */ - private function buildRestUserObject(RepositoryUser $user) + private function buildRestUserObject(RepositoryUser $user): Values\RestUser { return new Values\RestUser( $user, @@ -568,10 +485,8 @@ private function buildRestUserObject(RepositoryUser $user) /** * Loads user groups. - * - * @return \Ibexa\Rest\Server\Values\UserGroupList|\Ibexa\Rest\Server\Values\UserGroupRefList */ - public function loadUserGroups(Request $request) + public function loadUserGroups(Request $request): RestValue { $restUserGroups = []; if ($request->query->has('id')) { @@ -606,10 +521,8 @@ public function loadUserGroups(Request $request) /** * Loads a user group by its remote ID. - * - * @return \Ibexa\Rest\Server\Values\RestUserGroup */ - public function loadUserGroupByRemoteId(Request $request) + public function loadUserGroupByRemoteId(Request $request): Values\RestUserGroup { $contentInfo = $this->contentService->loadContentInfoByRemoteId($request->query->get('remoteId')); $userGroup = $this->userService->loadUserGroup($contentInfo->id, Language::ALL); @@ -632,7 +545,7 @@ public function loadUserGroupByRemoteId(Request $request) * * @return \Ibexa\Rest\Server\Values\RestUserGroup[] */ - public function loadUserGroupsAssignedToRole($roleId) + public function loadUserGroupsAssignedToRole($roleId): array { $role = $this->roleService->loadRole($roleId); $roleAssignments = $this->roleService->getRoleAssignments($role); @@ -661,12 +574,8 @@ public function loadUserGroupsAssignedToRole($roleId) /** * Loads drafts assigned to user. - * - * @param $userId - * - * @return \Ibexa\Rest\Server\Values\VersionList */ - public function loadUserDrafts($userId, Request $request) + public function loadUserDrafts(int $userId, Request $request): Values\VersionList { $contentDrafts = $this->contentService->loadContentDrafts( $this->userService->loadUser($userId) @@ -678,13 +587,10 @@ public function loadUserDrafts($userId, Request $request) /** * Moves the user group to another parent. * - * @param $groupPath - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\ResourceCreated + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function moveUserGroup($groupPath, Request $request) + public function moveUserGroup(string $groupPath, Request $request): Values\ResourceCreated { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -727,12 +633,8 @@ public function moveUserGroup($groupPath, Request $request) /** * Returns a list of the sub groups. - * - * @param $groupPath - * - * @return \Ibexa\Rest\Server\Values\UserGroupList|\Ibexa\Rest\Server\Values\UserGroupRefList */ - public function loadSubUserGroups($groupPath, Request $request) + public function loadSubUserGroups(string $groupPath, Request $request): RestValue { $offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0; $limit = $request->query->has('limit') ? (int)$request->query->get('limit') : 25; @@ -784,12 +686,8 @@ public function loadSubUserGroups($groupPath, Request $request) * Returns a list of user groups the user belongs to. * The returned list includes the resources for unassigning * a user group if the user is in multiple groups. - * - * @param $userId - * - * @return \Ibexa\Rest\Server\Values\UserGroupRefList */ - public function loadUserGroupsOfUser($userId, Request $request) + public function loadUserGroupsOfUser(int $userId, Request $request): RestValue { $offset = $request->query->has('offset') ? (int)$request->query->get('offset') : 0; $limit = $request->query->has('limit') ? (int)$request->query->get('limit') : 25; @@ -825,12 +723,8 @@ public function loadUserGroupsOfUser($userId, Request $request) /** * Loads the users of the group with the given path. - * - * @param $groupPath - * - * @return \Ibexa\Rest\Server\Values\UserList|\Ibexa\Rest\Server\Values\UserRefList */ - public function loadUsersFromGroup($groupPath, Request $request) + public function loadUsersFromGroup(string $groupPath, Request $request): RestValue { $userGroupLocation = $this->locationService->loadLocation( $this->extractLocationIdFromPath($groupPath) @@ -881,17 +775,14 @@ public function loadUsersFromGroup($groupPath, Request $request) /** * Unassigns the user from a user group. * - * @param $userId - * @param $groupPath - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\UserGroupRefList + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function unassignUserFromUserGroup($userId, $groupPath) + public function unassignUserFromUserGroup(int $userId, string $groupPath): Values\UserGroupRefList { $user = $this->userService->loadUser($userId); - $userGroupLocation = $this->locationService->loadLocation(trim($groupPath, '/')); + $userGroupLocation = $this->locationService->loadLocation((int)trim($groupPath, '/')); $userGroup = $this->userService->loadUserGroup( $userGroupLocation->contentId @@ -933,13 +824,10 @@ public function unassignUserFromUserGroup($userId, $groupPath) /** * Assigns the user to a user group. * - * @param $userId - * - * @throws \Ibexa\Rest\Server\Exceptions\ForbiddenException - * - * @return \Ibexa\Rest\Server\Values\UserGroupRefList + * @throws \Ibexa\Contracts\Rest\Exceptions\NotFoundException + * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException */ - public function assignUserToUserGroup($userId, Request $request) + public function assignUserToUserGroup(int $userId, Request $request): Values\UserGroupRefList { $user = $this->userService->loadUser($userId); @@ -991,94 +879,13 @@ public function assignUserToUserGroup($userId, Request $request) ); } - /** - * Creates a new session based on the credentials provided as POST parameters. - * - * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF - * - * @return Values\UserSession|Values\Conflict - * - * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). - */ - public function createSession(Request $request) - { - @trigger_error( - E_USER_DEPRECATED, - 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' - ); - - return $this->sessionController->createSessionAction($request); - } - - /** - * Refresh given session. - * - * @param string $sessionId - * - * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException if the CSRF token is missing or invalid - * - * @return \Ibexa\Rest\Server\Values\UserSession - * - * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). - */ - public function refreshSession($sessionId, Request $request) - { - @trigger_error( - E_USER_DEPRECATED, - 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' - ); - - return $this->sessionController->refreshSessionAction($sessionId, $request); - } - - /** - * Deletes given session. - * - * @param string $sessionId - * - * @return Values\DeletedUserSession|\Symfony\Component\HttpFoundation\Response - * - * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException if the CSRF token is missing or invalid - * @throws RestNotFoundException - * - * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). - */ - public function deleteSession($sessionId, Request $request) - { - @trigger_error( - E_USER_DEPRECATED, - 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' - ); - - return $this->sessionController->deleteSessionAction($sessionId, $request); - } - /** * Extracts and returns an item id from a path, e.g. /1/2/58 => 58. - * - * @param string $path - * - * @return mixed */ - private function extractLocationIdFromPath($path) + private function extractLocationIdFromPath(string $path): int { $pathParts = explode('/', $path); - return array_pop($pathParts); - } - - public function setTokenStorage(TokenStorageInterface $csrfTokenStorage) - { - @trigger_error( - E_USER_DEPRECATED, - 'setTokenStorage() is deprecated since 6.5 and will be removed in 7.0.' - ); - - $this->csrfTokenStorage = $csrfTokenStorage; - } - - public function setSessionController(SessionController $sessionController) - { - $this->sessionController = $sessionController; + return (int)array_pop($pathParts); } } diff --git a/src/lib/Server/Security/CsrfTokenManager.php b/src/lib/Server/Security/CsrfTokenManager.php index a9973101..1721a6e2 100644 --- a/src/lib/Server/Security/CsrfTokenManager.php +++ b/src/lib/Server/Security/CsrfTokenManager.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Security; @@ -13,17 +14,11 @@ use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; -class CsrfTokenManager extends BaseCsrfTokenManager +final class CsrfTokenManager extends BaseCsrfTokenManager { - /** - * @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface - */ - private $storage; + private TokenStorageInterface $storage; - /** - * @var string - */ - private $namespace; + private string $namespace; public function __construct( TokenGeneratorInterface $generator = null, @@ -36,26 +31,12 @@ public function __construct( parent::__construct($generator, $this->storage, $this->namespace); } - /** - * Tests if a CSRF token is stored. - * - * @param string $tokenId - * - * @return bool - */ - public function hasToken($tokenId) + public function hasToken(string $tokenId): bool { return $this->storage->hasToken($this->namespace . $tokenId); } - /** - * Resolves token namespace. - * - * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack - * - * @return string - */ - private function resolveNamespace(RequestStack $requestStack = null) + private function resolveNamespace(RequestStack $requestStack = null): string { if ($requestStack !== null && ($request = $requestStack->getMainRequest())) { return $request->isSecure() ? 'https-' : ''; diff --git a/src/lib/Server/Security/EventListener/SecurityListener.php b/src/lib/Server/Security/EventListener/SecurityListener.php index e7c6d28a..5e204274 100644 --- a/src/lib/Server/Security/EventListener/SecurityListener.php +++ b/src/lib/Server/Security/EventListener/SecurityListener.php @@ -21,8 +21,7 @@ */ final class SecurityListener implements EventSubscriberInterface { - /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ - private $permissionResolver; + private PermissionResolver $permissionResolver; public function __construct( PermissionResolver $permissionResolver diff --git a/src/lib/Server/Security/RestAuthenticator.php b/src/lib/Server/Security/RestAuthenticator.php deleted file mode 100644 index f0e948a3..00000000 --- a/src/lib/Server/Security/RestAuthenticator.php +++ /dev/null @@ -1,219 +0,0 @@ -tokenStorage = $tokenStorage; - $this->authenticationManager = $authenticationManager; - $this->providerKey = $providerKey; - $this->dispatcher = $dispatcher; - $this->configResolver = $configResolver; - $this->logger = $logger; - } - - /** - * Doesn't do anything as we don't use this service with main Firewall listener. - * - * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event - */ - public function __invoke(RequestEvent $event) - { - return; - } - - public function authenticate(Request $request) - { - // If a token already exists and username is the same as the one we request authentication for, - // then return it and mark it as coming from session. - $previousToken = $this->tokenStorage->getToken(); - if ( - $previousToken instanceof TokenInterface - && $previousToken->getUsername() === $request->attributes->get('username') - ) { - $previousToken->setAttribute('isFromSession', true); - - return $previousToken; - } - - $token = $this->attemptAuthentication($request); - if (!$token instanceof TokenInterface) { - if ($this->logger) { - $this->logger->error('REST: No token could be found in SecurityContext'); - } - - throw new TokenNotFoundException(); - } - - $this->tokenStorage->setToken($token); - $this->dispatcher->dispatch(new InteractiveLoginEvent($request, $token), SecurityEvents::INTERACTIVE_LOGIN); - - // Re-fetch token from SecurityContext since an INTERACTIVE_LOGIN listener might have changed it - // i.e. when using multiple user providers. - // @see \Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener::onInteractiveLogin() - $token = $this->tokenStorage->getToken(); - $user = $token->getUser(); - if (!$user instanceof IbexaUser) { - if ($this->logger) { - $this->logger->error('REST: Authenticated user must be Ibexa\\Core\\MVC\\Symfony\\Security\\User, got ' . is_string($user) ? $user : get_class($user)); - } - - $e = new InvalidUserTypeException('Authenticated user is not an Ibexa User.'); - $e->setToken($token); - throw $e; - } - - // Check if newly logged in user differs from previous one. - if ($this->isUserConflict($user, $previousToken)) { - $this->tokenStorage->setToken($previousToken); - throw new UserConflictException(); - } - - return $token; - } - - /** - * @param \Symfony\Component\HttpFoundation\Request $request - * - * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface - */ - private function attemptAuthentication(Request $request) - { - return $this->authenticationManager->authenticate( - new UsernamePasswordToken( - $request->attributes->get('username'), - $request->attributes->get('password'), - $this->providerKey - ) - ); - } - - /** - * Checks if newly matched user is conflicting with previously non-anonymous logged in user, if any. - * - * @param \Ibexa\Core\MVC\Symfony\Security\UserInterface $user - * @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $previousToken - * - * @return bool - */ - private function isUserConflict(IbexaUser $user, TokenInterface $previousToken = null) - { - if ($previousToken === null || !$previousToken instanceof UsernamePasswordToken) { - return false; - } - - $previousUser = $previousToken->getUser(); - if (!$previousUser instanceof IbexaUser) { - return false; - } - - $wasAnonymous = $previousUser->getAPIUser()->getUserId() == $this->configResolver->getParameter('anonymous_user_id'); - - // TODO: isEqualTo is not on the interface - return !$wasAnonymous && !$user->isEqualTo($previousUser); - } - - public function addLogoutHandler(LogoutHandlerInterface $handler) - { - $this->logoutHandlers[] = $handler; - } - - public function logout(Request $request) - { - $response = new Response(); - - // Manually clear the session through session storage. - // Session::invalidate() is not called on purpose, to avoid unwanted session migration that would imply - // generation of a new session id. - // REST logout must indeed clear the session cookie. - // See \Ibexa\Rest\Server\Security\RestLogoutHandler - $request->getSession()->clear(); - - $token = $this->tokenStorage->getToken(); - foreach ($this->logoutHandlers as $handler) { - // Explicitly ignore SessionLogoutHandler as we do session invalidation manually here, - // through the session storage, to avoid unwanted session migration. - if ($handler instanceof SessionLogoutHandler) { - continue; - } - - $handler->logout($request, $response, $token); - } - - return $response; - } -} diff --git a/src/lib/Server/Security/RestLogoutHandler.php b/src/lib/Server/Security/RestLogoutHandler.php deleted file mode 100644 index 78524b67..00000000 --- a/src/lib/Server/Security/RestLogoutHandler.php +++ /dev/null @@ -1,59 +0,0 @@ -configResolver = $configResolver; - } - - /** - * @param \Symfony\Component\HttpFoundation\Request $request - * @param \Symfony\Component\HttpFoundation\Response $response - * @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token - */ - public function logout(Request $request, Response $response, TokenInterface $token) - { - if (!$request->attributes->get('is_rest_request')) { - return; - } - - $path = '/'; - $domain = null; - - $session = $this->configResolver->getParameter('session'); - if (array_key_exists('cookie_domain', $session)) { - $domain = $session['cookie_domain']; - } - if (array_key_exists('cookie_path', $session)) { - $path = $session['cookie_path']; - } - - $response->headers->clearCookie($request->getSession()->getName(), $path, $domain); - } -} diff --git a/src/lib/Server/Values/DeletedUserSession.php b/src/lib/Server/Values/DeletedUserSession.php index a9d2b5d3..02f06699 100644 --- a/src/lib/Server/Values/DeletedUserSession.php +++ b/src/lib/Server/Values/DeletedUserSession.php @@ -4,22 +4,16 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Values; use Ibexa\Rest\Value as RestValue; use Symfony\Component\HttpFoundation\Response; -class DeletedUserSession extends RestValue +final class DeletedUserSession extends RestValue { - /** - * Response generated by RestAuthenticator. - * - * @see \Ibexa\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface::logout() - * - * @var \Symfony\Component\HttpFoundation\Response - */ - public $response; + public Response $response; public function __construct(Response $response) { diff --git a/src/lib/Server/Values/SessionInput.php b/src/lib/Server/Values/SessionInput.php index 828a11b5..6a5f9a8a 100644 --- a/src/lib/Server/Values/SessionInput.php +++ b/src/lib/Server/Values/SessionInput.php @@ -4,23 +4,15 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Values; use Ibexa\Contracts\Core\Repository\Values\ValueObject; -/** - * SessionInput view model. - */ -class SessionInput extends ValueObject +final class SessionInput extends ValueObject { - /** - * @var string - */ - public $login; + public string $login; - /** - * @var string - */ - public $password; + public string $password; } diff --git a/src/lib/Server/Values/UserSession.php b/src/lib/Server/Values/UserSession.php index 82f0946d..4eb52d9b 100644 --- a/src/lib/Server/Values/UserSession.php +++ b/src/lib/Server/Values/UserSession.php @@ -4,62 +4,34 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Rest\Server\Values; use Ibexa\Contracts\Core\Repository\Values\User\User; use Ibexa\Rest\Value as RestValue; -/** - * User list view model. - */ -class UserSession extends RestValue +final class UserSession extends RestValue { - /** - * User. - * - * @var \Ibexa\Contracts\Core\Repository\Values\User\User - */ - public $user; + public User $user; - /** - * Session name. - * - * @var string - */ - public $sessionName; + public string $sessionName; - /** - * Session ID. - * - * @var string - */ - public $sessionId; + public string $sessionId; - /** - * CSRF token value. - * - * @var string - */ - public $csrfToken; + public string $csrfToken; - /** - * True if session exists. - * - * @var bool - */ - public $exists; + public bool $exists; public bool $created; - /** - * @param \Ibexa\Contracts\Core\Repository\Values\User\User $user - * @param string $sessionName - * @param string $sessionId - * @param string $csrfToken - */ - public function __construct(User $user, $sessionName, $sessionId, $csrfToken, $created) - { + public function __construct( + User $user, + string $sessionName, + string $sessionId, + string $csrfToken, + bool $created + ) { $this->user = $user; $this->sessionName = $sessionName; $this->sessionId = $sessionId; diff --git a/tests/bundle/EventListener/CsrfListenerTest.php b/tests/bundle/EventListener/CsrfListenerTest.php index cb30cec8..50b8df83 100644 --- a/tests/bundle/EventListener/CsrfListenerTest.php +++ b/tests/bundle/EventListener/CsrfListenerTest.php @@ -4,12 +4,15 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\EventListener; use Ibexa\Bundle\Rest\EventListener\CsrfListener; use Ibexa\Core\Base\Exceptions\UnauthorizedException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\HeaderBag; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -17,97 +20,87 @@ use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -class CsrfListenerTest extends EventListenerTest +final class CsrfListenerTest extends EventListenerTest { - public const VALID_TOKEN = 'valid'; - public const INVALID_TOKEN = 'invalid'; - public const INTENTION = 'rest'; + public const string VALID_TOKEN = 'valid'; + public const string INVALID_TOKEN = 'invalid'; + public const string INTENTION = 'rest'; - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - protected $eventDispatcherMock; - - /** - * If set to null before initializing mocks, Request::getSession() is expected not to be called. - * - * @var \Symfony\Component\HttpFoundation\Session\SessionInterface - */ - protected $sessionMock; - - protected $sessionIsStarted = true; - - protected $csrfTokenHeaderValue = self::VALID_TOKEN; - - /** - * Route returned by Request::get( '_route' ) - * If set to false, get( '_route' ) is expected not to be called. - * - * @var string - */ - protected $route = 'ibexa.rest.something'; - - /** - * If set to false, Request::getRequestMethod() is expected not to be called. - */ - protected $requestMethod = 'POST'; - - public function provideExpectedSubscribedEventTypes() + public function provideExpectedSubscribedEventTypes(): array { return [ [[KernelEvents::REQUEST]], ]; } - public function testIsNotRestRequest() + public function testIsNotRestRequest(): void { - $this->isRestRequest = false; - - $this->requestMethod = false; - $this->sessionMock = false; - $this->route = false; - $this->csrfTokenHeaderValue = null; - $listener = $this->getEventListener(); - $listener->onKernelRequest($this->getEvent()); + $request = $this->createMock(Request::class); + $request->attributes = new ParameterBag(); + + $listener->onKernelRequest( + $this->getEvent($request) + ); } - public function testCsrfDisabled() + public function testCsrfDisabled(): void { - $this->requestMethod = false; - $this->sessionMock = false; - $this->route = false; - $this->csrfTokenHeaderValue = null; - - $this->getEventListener(false)->onKernelRequest($this->getEvent()); + $request = $this->createMock(Request::class); + $request->attributes = new ParameterBag([ + 'is_rest_request' => true, + ]); + + $this + ->getEventListener(false) + ->onKernelRequest($this->getEvent($request)); } - public function testNoSessionStarted() + public function testNoSessionStarted(): void { - $this->sessionIsStarted = false; - - $this->requestMethod = false; - $this->route = false; - $this->csrfTokenHeaderValue = null; - - $this->getEventListener()->onKernelRequest($this->getEvent()); + $request = $this->createMock(Request::class); + $request->attributes = new ParameterBag([ + 'is_rest_request' => true, + ]); + + $request + ->method('getSession') + ->willReturn($this->getSessionMock(false)); + + $this + ->getEventListener() + ->onKernelRequest($this->getEvent($request)); } /** * Tests that method CSRF check don't apply to are indeed ignored. * - * @param string $ignoredMethod - * * @dataProvider getIgnoredRequestMethods */ - public function testIgnoredRequestMethods($ignoredMethod) + public function testIgnoredRequestMethods(string $ignoredMethod): void { - $this->requestMethod = $ignoredMethod; - $this->route = false; - $this->csrfTokenHeaderValue = null; - - $this->getEventListener()->onKernelRequest($this->getEvent()); + $request = $this->createMock(Request::class); + $request->attributes = new ParameterBag([ + 'is_rest_request' => true, + ]); + + $request + ->method('getSession') + ->willReturn($this->getSessionMock()); + + $request + ->method('getMethod') + ->willReturn($ignoredMethod); + + $this + ->getEventListener() + ->onKernelRequest($this->getEvent($request)); } - public function getIgnoredRequestMethods() + /** + * @return array> + */ + public function getIgnoredRequestMethods(): array { return [ ['GET'], @@ -116,217 +109,187 @@ public function getIgnoredRequestMethods() ]; } - /** - * @dataProvider provideSessionRoutes - */ - public function testSessionRequests($route) + public function testSessionRequests(): void { - $this->route = $route; - $this->csrfTokenHeaderValue = null; + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); + $request->headers = $this->getRequestHeadersMock(); - $this->getEventListener()->onKernelRequest($this->getEvent()); - } + $request + ->method('getSession') + ->willReturn($this->getSessionMock()); - public static function provideSessionRoutes() - { - return [ - ['ibexa.rest.create_session'], - ['ibexa.rest.refresh_session'], - ['ibexa.rest.delete_session'], - ]; + $request + ->method('getMethod') + ->willReturn('GET'); + + $this + ->getEventListener() + ->onKernelRequest($this->getEvent($request)); } public function testSkipCsrfProtection(): void { - $this->enableCsrfProtection = false; - $this->csrfTokenHeaderValue = null; + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); + $request->headers = $this->getRequestHeadersMock(); - $listener = $this->getEventListener(); - $listener->onKernelRequest($this->getEvent()); + $this + ->getEventListener(false) + ->onKernelRequest($this->getEvent($request)); } - public function testNoHeader() + public function testNoHeader(): void { - $this->expectException(UnauthorizedException::class); + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); + $request->headers = $this->getRequestHeadersMock(); - $this->csrfTokenHeaderValue = false; + $request + ->method('getMethod') + ->willReturn('POST'); - $this->getEventListener()->onKernelRequest($this->getEvent()); - } + $request + ->method('getSession') + ->willReturn($this->getSessionMock()); - public function testInvalidToken() - { $this->expectException(UnauthorizedException::class); - $this->csrfTokenHeaderValue = self::INVALID_TOKEN; - - $this->getEventListener()->onKernelRequest($this->getEvent()); + $this + ->getEventListener() + ->onKernelRequest($this->getEvent($request)); } - public function testValidToken() + public function testInvalidToken(): void { - $this->getEventDispatcherMock() - ->expects(self::once()) - ->method('dispatch'); + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); + $request->headers = $this->getRequestHeadersMock(self::INVALID_TOKEN); - $this->getEventListener()->onKernelRequest($this->getEvent()); - } + $request + ->method('getMethod') + ->willReturn('POST'); - /** - * @return \Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface|\PHPUnit\Framework\MockObject\MockObject - */ - protected function getCsrfProviderMock() - { - $provider = $this->createMock(CsrfTokenManagerInterface::class); - $provider->expects(self::any()) - ->method('isTokenValid') - ->willReturnCallback( - static function (CsrfToken $token) { - if ($token == new CsrfToken(self::INTENTION, self::VALID_TOKEN)) { - return true; - } + $request + ->method('getSession') + ->willReturn($this->getSessionMock()); - return false; - } - ); + $this->expectException(UnauthorizedException::class); - return $provider; + $this + ->getEventListener() + ->onKernelRequest($this->getEvent($request)); } - /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Symfony\Component\HttpKernel\Event\RequestEvent - */ - protected function getEvent($class = null) + public function testValidToken(): void { - if (!isset($this->event)) { - parent::getEvent(RequestEvent::class); + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); + $request->headers = $this->getRequestHeadersMock(self::VALID_TOKEN); - $this->event - ->expects(self::any()) - ->method('getRequestType') - ->willReturn($this->requestType); - } + $request + ->method('getMethod') + ->willReturn('POST'); - return $this->event; + $request + ->method('getSession') + ->willReturn($this->getSessionMock()); + + $this + ->getEventListener(true, $this->getEventDispatcherMock()) + ->onKernelRequest($this->getEvent($request)); } - /** - * @return \Symfony\Component\HttpFoundation\Session\SessionInterface|\PHPUnit\Framework\MockObject\MockObject - */ - protected function getSessionMock() + protected function getEventListener( + ?bool $csrfEnabled = true, + ?EventDispatcherInterface $eventDispatcher = null + ): CsrfListener { + return new CsrfListener( + $eventDispatcher ?? $this->getEventDispatcherMock(), + $csrfEnabled ?? true, + self::INTENTION, + $csrfEnabled === true ? $this->getCsrfProviderMock() : null + ); + } + + private function getEvent(Request $request): RequestEvent { - if (!isset($this->sessionMock)) { - $this->sessionMock = $this->createMock(SessionInterface::class); - $this->sessionMock - ->expects(self::atLeastOnce()) - ->method('isStarted') - ->willReturn($this->sessionIsStarted); - } + $event = $this->createMock(RequestEvent::class); + $event + ->expects(self::once()) + ->method('getRequest') + ->willReturn($request); - return $this->sessionMock; + return $event; } /** - * @return \Symfony\Component\HttpFoundation\ParameterBag|\PHPUnit\Framework\MockObject\MockObject + * @return \Symfony\Component\HttpFoundation\Session\SessionInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected function getRequestHeadersMock() + private function getSessionMock(bool $isSessionStarted = true): SessionInterface { - if (!isset($this->requestHeadersMock)) { - $this->requestHeadersMock = parent::getRequestHeadersMock(); - - if ($this->csrfTokenHeaderValue === null) { - $this->requestHeadersMock - ->expects(self::never()) - ->method('has'); - - $this->requestHeadersMock - ->expects(self::never()) - ->method('get'); - } else { - $this->requestHeadersMock - ->expects(self::atLeastOnce()) - ->method('has') - ->with(CsrfListener::CSRF_TOKEN_HEADER) - ->willReturn(true); - - $this->requestHeadersMock - ->expects(self::atLeastOnce()) - ->method('get') - ->with(CsrfListener::CSRF_TOKEN_HEADER) - ->willReturn($this->csrfTokenHeaderValue); - } - } + $sessionMock = $this->createMock(SessionInterface::class); - return $this->requestHeadersMock; + $sessionMock + ->expects(self::atLeastOnce()) + ->method('isStarted') + ->willReturn($isSessionStarted); + + return $sessionMock; } /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Symfony\Component\HttpFoundation\Request + * @return \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected function getRequestMock() + private function getCsrfProviderMock(): CsrfTokenManagerInterface { - if (!isset($this->requestMock)) { - $this->requestMock = parent::getRequestMock(); - - if ($this->sessionMock === false) { - $this->requestMock - ->expects(self::never()) - ->method('getSession'); - } else { - $this->requestMock - ->expects(self::atLeastOnce()) - ->method('getSession') - ->willReturn($this->getSessionMock()); - } - - if ($this->route === false) { - $this->requestMock - ->expects(self::never()) - ->method('get'); - } else { - $this->requestMock - ->expects(self::atLeastOnce()) - ->method('get') - ->with('_route') - ->willReturn($this->route); - } - } + $provider = $this->createMock(CsrfTokenManagerInterface::class); + $provider->expects(self::any()) + ->method('isTokenValid') + ->willReturnCallback( + static function (CsrfToken $token): bool { + return + $token->getId() === self::INTENTION && + $token->getValue() === self::VALID_TOKEN; + } + ); - return $this->requestMock; + return $provider; } /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Symfony\Component\EventDispatcher\EventDispatcherInterface + * @return \Symfony\Component\HttpFoundation\HeaderBag&\PHPUnit\Framework\MockObject\MockObject */ - protected function getEventDispatcherMock() + private function getRequestHeadersMock(?string $csrfTokenHeaderValue = null): HeaderBag { - if (!isset($this->eventDispatcherMock)) { - $this->eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); + $headerBag = $this->createMock(HeaderBag::class); + + if ($csrfTokenHeaderValue === null) { + $headerBag + ->expects(self::never()) + ->method('get'); + } else { + $headerBag + ->expects(self::once()) + ->method('has') + ->with(CsrfListener::CSRF_TOKEN_HEADER) + ->willReturn($csrfTokenHeaderValue !== null); + + $headerBag + ->expects(self::once()) + ->method('get') + ->with(CsrfListener::CSRF_TOKEN_HEADER) + ->willReturn($csrfTokenHeaderValue); } - return $this->eventDispatcherMock; + return $headerBag; } /** - * @param bool $csrfEnabled - * - * @return \Ibexa\Bundle\Rest\EventListener\CsrfListener + * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface&\PHPUnit\Framework\MockObject\MockObject */ - protected function getEventListener($csrfEnabled = true) + private function getEventDispatcherMock(): EventDispatcherInterface { - if ($csrfEnabled) { - return new CsrfListener( - $this->getEventDispatcherMock(), - $csrfEnabled, - self::INTENTION, - $this->getCsrfProviderMock() - ); - } - - return new CsrfListener( - $this->getEventDispatcherMock(), - $csrfEnabled, - self::INTENTION - ); + return $this->createMock(EventDispatcherInterface::class); } } diff --git a/tests/bundle/EventListener/EventListenerTest.php b/tests/bundle/EventListener/EventListenerTest.php index 4521120c..2912846f 100644 --- a/tests/bundle/EventListener/EventListenerTest.php +++ b/tests/bundle/EventListener/EventListenerTest.php @@ -4,40 +4,32 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\EventListener; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\ParameterBag; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; abstract class EventListenerTest extends TestCase { - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - protected $event; - - /** @var \Symfony\Component\HttpFoundation\Request|\PHPUnit\Framework\MockObject\MockObject */ - protected $requestMock; - - /** @var \Symfony\Component\HttpFoundation\ParameterBag|\PHPUnit\Framework\MockObject\MockObject */ - protected $requestAttributesMock; - /** @var \Symfony\Component\HttpFoundation\ParameterBag|\PHPUnit\Framework\MockObject\MockObject */ - protected $requestHeadersMock; + protected ParameterBag $requestAttributesMock; - protected $isRestRequest = true; + protected bool $isRestRequest = true; - protected $requestType = HttpKernelInterface::MASTER_REQUEST; + protected int $requestType = HttpKernelInterface::MAIN_REQUEST; - protected $requestMethod = false; - - protected $enableCsrfProtection = true; + protected bool $enableCsrfProtection = true; /** + * @param array $expectedEventTypes + * * @dataProvider provideExpectedSubscribedEventTypes */ - public function testGetSubscribedEvents($expectedEventTypes) + public function testGetSubscribedEvents(array $expectedEventTypes): void { $eventListener = $this->getEventListener(); @@ -57,92 +49,32 @@ public function testGetSubscribedEvents($expectedEventTypes) } /** - * @return \PHPUnit\Framework\MockObject\MockObject|$class - */ - protected function getEvent($class) - { - if (!isset($this->event)) { - $this->event = $this->getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); - - $this->event - ->expects(self::any()) - ->method('getRequest') - ->willReturn($this->getRequestMock()); - } - - return $this->event; - } - - /** - * @return \Symfony\Component\HttpFoundation\ParameterBag|\PHPUnit\Framework\MockObject\MockObject - */ - protected function getRequestAttributesMock() - { - if (!isset($this->requestAttributesMock)) { - $this->requestAttributesMock = $this->createMock(ParameterBag::class); - $this->requestAttributesMock - ->expects(self::once()) - ->method('get') - ->with('is_rest_request') - ->willReturn($this->isRestRequest); - - $this->requestAttributesMock - ->method('getBoolean') - ->with('csrf_protection', true) - ->willReturn($this->enableCsrfProtection); - } - - return $this->requestAttributesMock; - } - - /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Symfony\Component\HttpFoundation\Request + * @return \Symfony\Component\HttpFoundation\ParameterBag&\PHPUnit\Framework\MockObject\MockObject */ - protected function getRequestMock() + protected function getRequestAttributesMock(): ParameterBag { - if (!isset($this->requestMock)) { - $this->requestMock = $this->createMock(Request::class); - $this->requestMock->attributes = $this->getRequestAttributesMock(); - $this->requestMock->headers = $this->getRequestHeadersMock(); - - if ($this->requestMethod === false) { - $this->requestMock - ->expects(self::never()) - ->method('getMethod'); - } else { - $this->requestMock - ->expects(self::atLeastOnce()) - ->method('getMethod') - ->willReturn($this->requestMethod); - } - } + $requestAttributesMock = $this->createMock(ParameterBag::class); - return $this->requestMock; - } + $requestAttributesMock + ->expects(self::any()) + ->method('get') + ->with('is_rest_request') + ->willReturn($this->isRestRequest); - /** - * @return \Symfony\Component\HttpFoundation\ParameterBag|\PHPUnit\Framework\MockObject\MockObject - */ - protected function getRequestHeadersMock() - { - if (!isset($this->requestHeadersMock)) { - $this->requestHeadersMock = $this->createMock(ParameterBag::class); - } + $requestAttributesMock + ->method('getBoolean') + ->with('csrf_protection', true) + ->willReturn($this->enableCsrfProtection); - return $this->requestHeadersMock; + return $requestAttributesMock; } - /** - * @param bool $csrfEnabled - * - * @return \Ibexa\Bundle\Rest\EventListener\CsrfListener - */ - abstract protected function getEventListener(); + abstract protected function getEventListener(?bool $csrfEnabled = null): EventSubscriberInterface; /** * Returns an array with the events the listener should be subscribed to. + * + * @return array */ - abstract public function provideExpectedSubscribedEventTypes(); + abstract public function provideExpectedSubscribedEventTypes(): array; } diff --git a/tests/bundle/EventListener/RequestListenerTest.php b/tests/bundle/EventListener/RequestListenerTest.php index ee9d0879..f248c18f 100644 --- a/tests/bundle/EventListener/RequestListenerTest.php +++ b/tests/bundle/EventListener/RequestListenerTest.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\EventListener; @@ -17,8 +18,8 @@ final class RequestListenerTest extends EventListenerTest { - public const REST_ROUTE = '/api/ibexa/v2/rest-route'; - public const NON_REST_ROUTE = '/non-rest-route'; + public const string REST_ROUTE = '/api/ibexa/v2/rest-route'; + public const string NON_REST_ROUTE = '/non-rest-route'; /** * @return array @@ -33,7 +34,7 @@ public function provideExpectedSubscribedEventTypes(): array } /** - * @retirm array + * @return array */ public static function getDataForTestOnKernelRequest(): array { @@ -87,7 +88,7 @@ public function testOnKernelRequest(string $uri, bool $isExpectedRestRequest): v self::assertSame($isExpectedRestRequest, $request->attributes->get('is_rest_request')); } - protected function getEventListener(): RequestListener + protected function getEventListener(?bool $csrfEnabled = null): RequestListener { return new RequestListener( new UriParser($this->createMock(UrlMatcherInterface::class)) diff --git a/tests/bundle/EventListener/ResponseListenerTest.php b/tests/bundle/EventListener/ResponseListenerTest.php index cee6aa0d..8f85453f 100644 --- a/tests/bundle/EventListener/ResponseListenerTest.php +++ b/tests/bundle/EventListener/ResponseListenerTest.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\EventListener; @@ -11,36 +12,32 @@ use Ibexa\Bundle\Rest\EventListener\ResponseListener; use Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher; use stdClass; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ViewEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -class ResponseListenerTest extends EventListenerTest +final class ResponseListenerTest extends EventListenerTest { - /** @var \Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher|\PHPUnit\Framework\MockObject\MockObject */ - protected $visitorDispatcherMock; + /** @var \Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher&\PHPUnit\Framework\MockObject\MockObject */ + protected AcceptHeaderVisitorDispatcher $visitorDispatcherMock; - /** @var \stdClass */ - protected $eventValue; + protected stdClass $eventValue; - /** @var \Exception */ - protected $exceptionEventValue; + protected Exception $exceptionEventValue; - protected $dispatcherMessage; + protected Response $response; - protected $controllerResult; - - /** @var \Symfony\Component\HttpFoundation\Response */ - protected $response; - - /** @var \Symfony\Contracts\EventDispatcher\Event */ - protected $event; + protected EventDispatcherInterface $event; /** @var \Symfony\Component\HttpKernel\KernelInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $kernelMock; + protected KernelInterface $kernelMock; public function setUp(): void { @@ -49,24 +46,24 @@ public function setUp(): void $this->response = new Response('BODY', 406, ['foo' => 'bar']); } - public function provideExpectedSubscribedEventTypes() + public function provideExpectedSubscribedEventTypes(): array { return [ [[KernelEvents::VIEW, KernelEvents::EXCEPTION]], ]; } - public function testOnKernelResultViewIsNotRestRequest() + public function testOnKernelResultViewIsNotRestRequest(): void { $this->isRestRequest = false; $this->onKernelViewIsNotRestRequest( 'onKernelResultView', - $this->getControllerResultEvent() + $this->getViewEvent() ); } - public function testOnKernelExceptionViewIsNotRestRequest() + public function testOnKernelExceptionViewIsNotRestRequest(): void { $this->isRestRequest = false; @@ -76,7 +73,7 @@ public function testOnKernelExceptionViewIsNotRestRequest() ); } - protected function onKernelViewIsNotRestRequest($method, RequestEvent $event) + protected function onKernelViewIsNotRestRequest(string $method, RequestEvent $event): void { $this->getVisitorDispatcherMock() ->expects(self::never()) @@ -85,18 +82,32 @@ protected function onKernelViewIsNotRestRequest($method, RequestEvent $event) $this->getEventListener()->$method($event); } - public function testOnKernelExceptionView() + public function testOnKernelExceptionView(): void { - $this->onKernelView('onKernelExceptionView', $this->getExceptionEvent(), $this->exceptionEventValue); + $this->onKernelView( + 'onKernelExceptionView', + $this->getExceptionEvent(), + $this->exceptionEventValue + ); } - public function testOnControllerResultView() + public function testOnControllerResultView(): void { - $this->onKernelView('onKernelResultView', $this->getControllerResultEvent(), $this->eventValue); + $this->onKernelView( + 'onKernelResultView', + $this->getViewEvent(), + $this->eventValue + ); } - protected function onKernelView($method, $event, $value) - { + /** + * @param mixed $value + */ + protected function onKernelView( + string $method, + RequestEvent $event, + $value + ): void { $this->getVisitorDispatcherMock() ->expects(self::once()) ->method('dispatch') @@ -113,9 +124,9 @@ protected function onKernelView($method, $event, $value) } /** - * @return \Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher|\PHPUnit\Framework\MockObject\MockObject + * @return \Ibexa\Rest\Server\View\AcceptHeaderVisitorDispatcher&\PHPUnit\Framework\MockObject\MockObject */ - public function getVisitorDispatcherMock() + private function getVisitorDispatcherMock(): AcceptHeaderVisitorDispatcher { if (!isset($this->visitorDispatcherMock)) { $this->visitorDispatcherMock = $this->createMock(AcceptHeaderVisitorDispatcher::class); @@ -124,59 +135,49 @@ public function getVisitorDispatcherMock() return $this->visitorDispatcherMock; } - /** - * @return \Ibexa\Bundle\Rest\EventListener\ResponseListener - */ - protected function getEventListener() + protected function getEventListener(?bool $csrfEnabled = null): EventSubscriberInterface { return new ResponseListener( $this->getVisitorDispatcherMock() ); } - /** - * @return \Symfony\Component\HttpKernel\Event\ViewEvent - */ - protected function getControllerResultEvent(): ViewEvent + protected function getViewEvent(): ViewEvent { - if (!isset($this->event)) { - $this->event = new ViewEvent( - $this->getKernelMock(), - $this->getRequestMock(), - KernelInterface::MASTER_REQUEST, - $this->eventValue - ); - } - - return $this->event; + return new ViewEvent( + $this->getKernelMock(), + $this->getRequestMock(), + HttpKernelInterface::MAIN_REQUEST, + $this->eventValue + ); } /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Symfony\Component\HttpKernel\KernelInterface + * @return \PHPUnit\Framework\MockObject\MockObject&\Symfony\Component\HttpKernel\KernelInterface */ protected function getKernelMock(): KernelInterface { - if (!isset($this->kernelMock)) { - $this->kernelMock = $this->createMock(KernelInterface::class); - } + return $this->createMock(KernelInterface::class); + } - return $this->kernelMock; + private function getExceptionEvent(): ExceptionEvent + { + return new ExceptionEvent( + $this->getKernelMock(), + $this->getRequestMock(), + HttpKernelInterface::MAIN_REQUEST, + $this->exceptionEventValue + ); } /** - * @return \Symfony\Component\HttpKernel\Event\ExceptionEvent + * @return \Symfony\Component\HttpFoundation\Request&\PHPUnit\Framework\MockObject\MockObject */ - protected function getExceptionEvent(): ExceptionEvent + private function getRequestMock(): Request { - if (!isset($this->event)) { - $this->event = new ExceptionEvent( - $this->getKernelMock(), - $this->getRequestMock(), - KernelInterface::MASTER_REQUEST, - $this->exceptionEventValue - ); - } + $request = $this->createMock(Request::class); + $request->attributes = $this->getRequestAttributesMock(); - return $this->event; + return $request; } } diff --git a/tests/bundle/Functional/BinaryContentTest.php b/tests/bundle/Functional/BinaryContentTest.php index 100af9f7..bb8f9a39 100644 --- a/tests/bundle/Functional/BinaryContentTest.php +++ b/tests/bundle/Functional/BinaryContentTest.php @@ -4,13 +4,14 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\Functional; use Ibexa\Tests\Bundle\Rest\Functional\TestCase as RESTFunctionalTestCase; use Symfony\Component\HttpFoundation\Response; -class BinaryContentTest extends RESTFunctionalTestCase +final class BinaryContentTest extends RESTFunctionalTestCase { public function testCreateContentWithImageData(): string { @@ -77,7 +78,7 @@ public function testCreateContentWithImageData(): string /** * @depends testCreateContentWithImageData */ - public function testGetImageVariation(string $hrefToImage) + public function testGetImageVariation(string $hrefToImage): void { $imageResponse = $this->sendHttpRequest( $this->createHttpRequest( @@ -88,10 +89,10 @@ public function testGetImageVariation(string $hrefToImage) ) ); - $jsonResponse = json_decode($imageResponse->getBody()); + $jsonResponse = json_decode($imageResponse->getBody()->getContents()); $imageField = $jsonResponse->Version->Fields->field[2]; - self::assertObjectHasAttribute('variations', $imageField->fieldValue); + self::assertObjectHasProperty('variations', $imageField->fieldValue); $variationResponse = $this->sendHttpRequest( $this->createHttpRequest( @@ -99,13 +100,14 @@ public function testGetImageVariation(string $hrefToImage) $imageField->fieldValue->variations->medium->href, ) ); + self::assertHttpResponseCodeEquals($variationResponse, Response::HTTP_OK); } /** * @depends testCreateContentWithImageData */ - public function testGetImageAssetVariations(string $hrefToImage) + public function testGetImageAssetVariations(string $hrefToImage): void { $parsedHref = explode('/', $hrefToImage); $destinationContentId = end($parsedHref); @@ -159,7 +161,7 @@ public function testGetImageAssetVariations(string $hrefToImage) self::assertHttpResponseCodeEquals($variationResponse, Response::HTTP_OK); } - private function createContentTypeWithImageAsset() + private function createContentTypeWithImageAsset(): string { $body = <<< XML diff --git a/tests/bundle/Functional/SessionTest.php b/tests/bundle/Functional/SessionTest.php index f5e98e9d..db4124f2 100644 --- a/tests/bundle/Functional/SessionTest.php +++ b/tests/bundle/Functional/SessionTest.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\Functional; @@ -13,41 +14,34 @@ use Psr\Http\Message\ResponseInterface; use stdClass; -class SessionTest extends TestCase +final class SessionTest extends TestCase { public function setUp(): void { $this->autoLogin = false; + parent::setUp(); } - public function testCreateSessionBadCredentials() + public function testCreateSessionBadCredentials(): void { $request = $this->createAuthenticationHttpRequest('admin', 'bad_password'); $response = $this->sendHttpRequest($request); - self::assertHttpResponseCodeEquals($response, 401); - } - /** - * @return \stdClass The login request's response - */ - public function testCreateSession() - { - return $this->login(); + self::assertHttpResponseCodeEquals($response, 500); } /** * @depends testCreateSession - * - * @param \stdClass $session */ - public function testRefreshSession(stdClass $session) + public function testRefreshSession(stdClass $session): void { $response = $this->sendHttpRequest($this->createRefreshRequest($session)); + self::assertHttpResponseCodeEquals($response, 200); } - public function testRefreshSessionExpired() + public function testRefreshSessionExpired(): void { $session = $this->login(); @@ -60,7 +54,7 @@ public function testRefreshSessionExpired() self::assertHttpResponseDeletesSessionCookie($session, $response); } - public function testRefreshSessionMissingCsrfToken() + public function testRefreshSessionMissingCsrfToken(): void { $session = $this->login(); @@ -68,33 +62,39 @@ public function testRefreshSessionMissingCsrfToken() ->createRefreshRequest($session) ->withoutHeader('X-CSRF-Token'); $response = $this->sendHttpRequest($refreshRequest); + self::assertHttpResponseCodeEquals($response, 401); } - public function testDeleteSession() + public function testCreateSession(): stdClass + { + return $this->login(); + } + + public function testDeleteSession(): void { $session = $this->login(); $response = $this->sendHttpRequest($this->createDeleteRequest($session)); + self::assertHttpResponseCodeEquals($response, 204); self::assertHttpResponseDeletesSessionCookie($session, $response); - - return $session; } /** * CSRF needs to be tested as session handling bypasses the CsrfListener. */ - public function testDeleteSessionMissingCsrfToken() + public function testDeleteSessionMissingCsrfToken(): void { $session = $this->login(); $request = $this ->createDeleteRequest($session) ->withoutHeader('X-CSRF-Token'); $response = $this->sendHttpRequest($request); + self::assertHttpResponseCodeEquals($response, 401); } - public function testLoginWithExistingFrontendSession() + public function testLoginWithExistingFrontendSession(): void { $baseURI = $this->getBaseURI(); $browser = $this->createBrowser(); @@ -137,14 +137,35 @@ public function testLoginWithExistingFrontendSession() self::assertHttpResponseCodeEquals($response, 201); } - /** - * @depends testDeleteSession - */ - public function testDeleteSessionExpired($session) + public function testDeleteSessionExpired(): void { - $response = $this->sendHttpRequest($this->createDeleteRequest($session)); - self::assertHttpResponseCodeEquals($response, 404); + $session = $this->login(); + $deleteSessionRequest = $this->createDeleteRequest($session); + + $response = $this->sendHttpRequest($deleteSessionRequest); + + self::assertHttpResponseCodeEquals($response, 204); self::assertHttpResponseDeletesSessionCookie($session, $response); + + //triggered again to make sure deleting already deleted session results in 404 + $response = $this->sendHttpRequest($deleteSessionRequest); + + self::assertHttpResponseCodeEquals($response, 404); + } + + protected function createRefreshRequest(stdClass $session): RequestInterface + { + return $this->createHttpRequest( + 'POST', + sprintf('/api/ibexa/v2/user/sessions/%s/refresh', $session->identifier), + '', + 'Session+json', + '', + [ + 'Cookie' => sprintf('%s=%s', $session->name, $session->identifier), + 'X-CSRF-Token' => $session->csrfToken, + ] + ); } /** @@ -164,8 +185,10 @@ public function testCheckSession(): void 'X-CSRF-Token' => $session->csrfToken, ] ); + $response = $this->sendHttpRequest($request); self::assertHttpResponseCodeEquals($response, 200); + $contents = $response->getBody()->getContents(); $data = json_decode($contents, true, JSON_THROW_ON_ERROR); self::assertArrayHasKey('Session', $data); @@ -182,42 +205,17 @@ public function testCheckSessionWithoutOne(): void '', 'Session+json' ); + $response = $this->sendHttpRequest($request); self::assertHttpResponseCodeEquals($response, 404); + $contents = $response->getBody()->getContents(); self::assertEmpty($contents); } - /** - * @param \stdClass $session - * - * @return \Psr\Http\Message\RequestInterface - */ - protected function createRefreshRequest(stdClass $session): RequestInterface + private function createDeleteRequest(stdClass $session): RequestInterface { - $request = $this->createHttpRequest( - 'POST', - sprintf('/api/ibexa/v2/user/sessions/%s/refresh', $session->identifier), - '', - 'Session+json', - '', - [ - 'Cookie' => sprintf('%s=%s', $session->name, $session->identifier), - 'X-CSRF-Token' => $session->csrfToken, - ] - ); - - return $request; - } - - /** - * @param \stdClass $session - * - * @return \Psr\Http\Message\RequestInterface - */ - protected function createDeleteRequest(stdClass $session): RequestInterface - { - $deleteRequest = $this->createHttpRequest( + return $this->createHttpRequest( 'DELETE', $session->_href, '', @@ -228,12 +226,12 @@ protected function createDeleteRequest(stdClass $session): RequestInterface 'X-CSRF-Token' => $session->csrfToken, ] ); - - return $deleteRequest; } - private static function assertHttpResponseDeletesSessionCookie($session, ResponseInterface $response) - { + private static function assertHttpResponseDeletesSessionCookie( + stdClass $session, + ResponseInterface $response + ): void { self::assertStringStartsWith("{$session->name}=deleted;", $response->getHeader('set-cookie')[0]); } } diff --git a/tests/bundle/Functional/UserTest.php b/tests/bundle/Functional/UserTest.php index 63a5482f..54fd1902 100644 --- a/tests/bundle/Functional/UserTest.php +++ b/tests/bundle/Functional/UserTest.php @@ -4,19 +4,20 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Bundle\Rest\Functional; use Ibexa\Tests\Bundle\Rest\Functional\TestCase as RESTFunctionalTestCase; -class UserTest extends RESTFunctionalTestCase +final class UserTest extends RESTFunctionalTestCase { private const HEADER_LOCATION = 'Location'; /** * Covers GET /user/groups/root. */ - public function loadRootUserGroup() + public function loadRootUserGroup(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', '/api/ibexa/v2/user/groups/root') @@ -28,9 +29,9 @@ public function loadRootUserGroup() /** * Covers POST /user/groups/{groupPath}/subgroups. * - * @return string the created user group href + * returns the created user group href */ - public function testCreateUserGroup() + public function testCreateUserGroup(): string { $text = $this->addTestSuffix(__FUNCTION__); $xml = <<< XML @@ -71,11 +72,11 @@ public function testCreateUserGroup() } /** - * @param $userGroupId Covers GET /user/groups/{groupId} + * $groupId covers GET /user/groups/{groupId}. * * @depends testCreateUserGroup */ - public function testLoadUserGroup($groupId) + public function testLoadUserGroup(string $groupId): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', $groupId) @@ -89,7 +90,7 @@ public function testLoadUserGroup($groupId) * * @depends testCreateUserGroup */ - public function testUpdateUserGroup($groupHref) + public function testUpdateUserGroup(string $groupHref): void { $text = $this->addTestSuffix(__FUNCTION__); $xml = <<< XML @@ -118,11 +119,12 @@ public function testUpdateUserGroup($groupHref) /** * @depends testCreateUserGroup + * * Covers POST /user/groups/{groupPath}/users * - * @return string The created user href + * returns created user href */ - public function testCreateUser($userGroupHref) + public function testCreateUser(string $userGroupHref): string { $text = $this->addTestSuffix(__FUNCTION__); $xml = <<< XML @@ -166,11 +168,9 @@ public function testCreateUser($userGroupHref) } /** - * @param $userId Covers GET /user/users/{userId} - * * @depends testCreateUser */ - public function testLoadUser($userHref) + public function testLoadUser(string $userHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', $userHref) @@ -203,11 +203,11 @@ public function testRedirectToCurrentUserWhenNotLoggedIn(): void /** * @depends testCreateUser + * * Covers PATCH /user/users/{userId} */ - public function testUpdateUser($userHref) + public function testUpdateUser(string $userHref): void { - $text = $this->addTestSuffix(__FUNCTION__); $xml = <<< XML @@ -235,7 +235,7 @@ public function testUpdateUser($userHref) /** * Covers GET /user/users. */ - public function testLoadUsers() + public function testLoadUsers(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', '/api/ibexa/v2/user/users') @@ -246,9 +246,10 @@ public function testLoadUsers() /** * @depends testCreateUser + * * Covers GET /user/users?remoteId={userRemoteId} */ - public function testLoadUserByRemoteId() + public function testLoadUserByRemoteId(): void { $remoteId = $this->addTestSuffix('testCreateUser'); $response = $this->sendHttpRequest( @@ -261,7 +262,7 @@ public function testLoadUserByRemoteId() /** * Covers GET /user/groups. */ - public function testLoadUserGroups() + public function testLoadUserGroups(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', '/api/ibexa/v2/user/groups') @@ -272,9 +273,10 @@ public function testLoadUserGroups() /** * @depends testCreateUserGroup + * * Covers GET /user/groups?remoteId={groupRemoteId} */ - public function testLoadUserGroupByRemoteId($groupHref) + public function testLoadUserGroupByRemoteId(): void { $remoteId = $this->addTestSuffix('testCreateUserGroup'); $response = $this->sendHttpRequest( @@ -289,7 +291,7 @@ public function testLoadUserGroupByRemoteId($groupHref) * * @depends testCreateUser */ - public function testLoadUserDrafts($userHref) + public function testLoadUserDrafts(string $userHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', "$userHref/drafts") @@ -300,9 +302,10 @@ public function testLoadUserDrafts($userHref) /** * @depends testCreateUserGroup + * * Covers GET /user/groups/{groupPath}/subgroups */ - public function testLoadSubUserGroups($groupHref) + public function testLoadSubUserGroups(string $groupHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', "$groupHref/subgroups") @@ -316,7 +319,7 @@ public function testLoadSubUserGroups($groupHref) * * @depends testCreateUser */ - public function testLoadUserGroupsOfUser($userHref) + public function testLoadUserGroupsOfUser(string $userHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', "$userHref/groups") @@ -330,7 +333,7 @@ public function testLoadUserGroupsOfUser($userHref) * * @depends testCreateUserGroup */ - public function testLoadUsersFromGroup($groupHref) + public function testLoadUsersFromGroup(string $groupHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', "$groupHref/users") @@ -343,10 +346,8 @@ public function testLoadUsersFromGroup($groupHref) * Covers POST /user/users/{userId}/groups. * * @depends testCreateUser - * - * @return string $userHref */ - public function testAssignUserToUserGroup($userHref) + public function testAssignUserToUserGroup(string $userHref): string { // /1/5/12 is Members $response = $this->sendHttpRequest( @@ -363,7 +364,7 @@ public function testAssignUserToUserGroup($userHref) * * @depends testAssignUserToUserGroup */ - public function testUnassignUserFromUserGroup($userHref) + public function testUnassignUserFromUserGroup(string $userHref): void { // /1/5/12 is Members $response = $this->sendHttpRequest( @@ -378,7 +379,7 @@ public function testUnassignUserFromUserGroup($userHref) * * @depends testCreateUserGroup */ - public function testMoveUserGroup($groupHref) + public function testMoveUserGroup(string $groupHref): void { $request = $this->createHttpRequest( 'MOVE', @@ -395,11 +396,10 @@ public function testMoveUserGroup($groupHref) /** * @depends testCreateUser - * Covers POST /user/sessions * - * @return string The created session href + * Covers POST /user/sessions */ - public function testCreateSession() + public function testCreateSession(): string { self::markTestSkipped('@todo fixme'); @@ -431,13 +431,15 @@ public function testCreateSession() /** * @depends testCreateSession + * * Covers DELETE /user/sessions/{sessionId} */ - public function testDeleteSession($sessionHref) + public function testDeleteSession(string $sessionHref): void { self::markTestSkipped('@todo improve. The session can only be deleted if started !'); + $response = $this->sendHttpRequest( - $request = $this->createHttpRequest('DELETE', $sessionHref) + $this->createHttpRequest('DELETE', $sessionHref) ); self::assertHttpResponseCodeEquals($response, 204); @@ -445,9 +447,10 @@ public function testDeleteSession($sessionHref) /** * @depends testCreateUser + * * Covers DELETE /user/users/{userId} */ - public function testDeleteUser($userHref) + public function testDeleteUser(string $userHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('DELETE', $userHref) @@ -458,9 +461,10 @@ public function testDeleteUser($userHref) /** * @depends testCreateUserGroup + * * Covers DELETE /user/users/{userId} */ - public function testDeleteUserGroup($groupHref) + public function testDeleteUserGroup(string $groupHref): void { $response = $this->sendHttpRequest( $this->createHttpRequest('DELETE', $groupHref) @@ -469,7 +473,7 @@ public function testDeleteUserGroup($groupHref) self::assertHttpResponseCodeEquals($response, 204); } - public function testFilterUsersByLoginQueryParameter() + public function testFilterUsersByLoginQueryParameter(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', '/api/ibexa/v2/user/users?login=admin') @@ -484,7 +488,7 @@ public function testFilterUsersByLoginQueryParameter() self::assertHttpResponseCodeEquals($response404, 404); } - public function testIfLoginIsUsedByAnotherUser() + public function testIfLoginIsUsedByAnotherUser(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('HEAD', '/api/ibexa/v2/user/users?login=admin') @@ -499,7 +503,7 @@ public function testIfLoginIsUsedByAnotherUser() self::assertHttpResponseCodeEquals($response404, 404); } - public function testFilterUsersByEmailQueryParameter() + public function testFilterUsersByEmailQueryParameter(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('GET', '/api/ibexa/v2/user/users?email=admin@link.invalid') @@ -514,7 +518,7 @@ public function testFilterUsersByEmailQueryParameter() self::assertHttpResponseCodeEquals($response404, 404); } - public function testIfEmailIsUsedByAnotherUser() + public function testIfEmailIsUsedByAnotherUser(): void { $response = $this->sendHttpRequest( $this->createHttpRequest('HEAD', '/api/ibexa/v2/user/users?email=admin@link.invalid') diff --git a/tests/integration/UriParser/UriParserTest.php b/tests/integration/UriParser/UriParserTest.php index eb7ba0c2..87dc95eb 100644 --- a/tests/integration/UriParser/UriParserTest.php +++ b/tests/integration/UriParser/UriParserTest.php @@ -39,11 +39,11 @@ public static function getDataForTestGetAttributeFromUri(): iterable '2', ]; - yield 'Get sessionId attribute from ibexa.rest.refresh_session POST route' => [ + yield 'Get userId attribute from ibexa.rest.assign_user_to_user_group POST route' => [ 'POST', - '/api/ibexa/v2/user/sessions/MySession/refresh', - 'sessionId', - 'MySession', + '/api/ibexa/v2/user/users/14/groups', + 'userId', + '14', ]; } @@ -66,19 +66,19 @@ public function testGetAttributeFromUri( */ public static function getDataForTestGetAttributeFromUriThrowsException(): iterable { - $uri = '/api/ibexa/v2/user/sessions/MySession/refresh'; + $uri = '/api/ibexa/v2/content/sections/1'; yield 'Invalid attribute' => [ - 'POST', + 'GET', $uri, 'session', "No attribute 'session' in route matched from $uri", ]; yield 'Invalid method' => [ - 'GET', + 'POST', $uri, - 'sessionId', - "Method 'GET' is not allowed for '$uri'. Allowed: [POST]", + 'sectionId', + "Method 'POST' is not allowed for '$uri'. Allowed: [GET, PATCH, DELETE]", ]; yield 'Invalid route' => [ @@ -113,6 +113,9 @@ public function testGetAttributeFromUriThrowsException( $this->uriParser->getAttributeFromUri($uri, $attributeName, $method); } + /** + * @return iterable + */ public static function getDataForTestIsRestRequest(): iterable { yield ($uri = '/api/ibexa/v2/foo') => [ diff --git a/tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php b/tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php index 3091b20d..f85be13f 100644 --- a/tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php +++ b/tests/lib/Server/Input/Parser/FieldDefinitionUpdateTest.php @@ -4,6 +4,7 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ +declare(strict_types=1); namespace Ibexa\Tests\Rest\Server\Input\Parser; @@ -20,12 +21,9 @@ /** * @todo Test with fieldSettings and validatorConfiguration when specified */ -class FieldDefinitionUpdateTest extends BaseTest +final class FieldDefinitionUpdateTest extends BaseTest { - /** - * Tests the FieldDefinitionUpdate parser. - */ - public function testParse() + public function testParse(): void { $inputArray = $this->getInputArray(); @@ -112,10 +110,7 @@ public function testParse() ); } - /** - * Test FieldDefinitionUpdate parser throwing exception on invalid names. - */ - public function testParseExceptionOnInvalidNames() + public function testParseExceptionOnInvalidNames(): void { $this->expectException(Parser::class); $this->expectExceptionMessage('Invalid \'names\' element for FieldDefinitionUpdate.'); @@ -126,10 +121,7 @@ public function testParseExceptionOnInvalidNames() $fieldDefinitionUpdate->parse($inputArray, $this->getParsingDispatcherMock()); } - /** - * Test FieldDefinitionUpdate parser throwing exception on invalid descriptions. - */ - public function testParseExceptionOnInvalidDescriptions() + public function testParseExceptionOnInvalidDescriptions(): void { $this->expectException(Parser::class); $this->expectExceptionMessage('Invalid \'descriptions\' element for FieldDefinitionUpdate.'); @@ -140,12 +132,7 @@ public function testParseExceptionOnInvalidDescriptions() $fieldDefinitionUpdate->parse($inputArray, $this->getParsingDispatcherMock()); } - /** - * Returns the FieldDefinitionUpdate parser. - * - * @return \Ibexa\Rest\Server\Input\Parser\FieldDefinitionUpdate - */ - protected function internalGetParser() + protected function internalGetParser(): FieldDefinitionUpdate { return new FieldDefinitionUpdate( $this->getContentTypeServiceMock(), @@ -154,12 +141,7 @@ protected function internalGetParser() ); } - /** - * Get the FieldTypeParser mock object. - * - * @return \Ibexa\Rest\Input\FieldTypeParser - */ - protected function getFieldTypeParserMock() + protected function getFieldTypeParserMock(): FieldTypeParser { $fieldTypeParserMock = $this->createMock(FieldTypeParser::class); @@ -185,12 +167,7 @@ protected function getFieldTypeParserMock() return $fieldTypeParserMock; } - /** - * Get the content type service mock object. - * - * @return \Ibexa\Contracts\Core\Repository\ContentTypeService - */ - protected function getContentTypeServiceMock() + protected function getContentTypeServiceMock(): ContentTypeService { $contentTypeServiceMock = $this->createMock(ContentTypeService::class); @@ -212,6 +189,7 @@ protected function getContentTypeServiceMock() [ 'id' => 24, 'fieldTypeIdentifier' => 'ezstring', + 'identifier' => 'title', ] ), ]), @@ -224,11 +202,9 @@ protected function getContentTypeServiceMock() } /** - * Returns the array under test. - * - * @return array + * @return array */ - protected function getInputArray() + protected function getInputArray(): array { return [ '__url' => '/content/types/42/draft/fieldDefinitions/24', @@ -268,7 +244,10 @@ protected function getInputArray() ]; } - public function getParseHrefExpectationsMap() + /** + * @return array{array{string, string, int}} + */ + public function getParseHrefExpectationsMap(): array { return [ ['/content/types/42/draft/fieldDefinitions/24', 'contentTypeId', 42], diff --git a/tests/lib/Server/Security/CsrfTokenManagerTest.php b/tests/lib/Server/Security/CsrfTokenManagerTest.php deleted file mode 100644 index 0f3d32cb..00000000 --- a/tests/lib/Server/Security/CsrfTokenManagerTest.php +++ /dev/null @@ -1,77 +0,0 @@ -tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->requestStack = $this->createMock(RequestStack::class); - } - - public function testHasTokenForHttp() - { - $csrfTokenManager = $this->createCsrfTokenManager(false); - - $this->tokenStorage - ->expects(self::once()) - ->method('hasToken') - ->with(self::CSRF_TOKEN_INTENTION); - - $csrfTokenManager->hasToken(self::CSRF_TOKEN_INTENTION); - } - - public function testHasTokenForHttps() - { - $csrfTokenManager = $this->createCsrfTokenManager(true); - - $this->tokenStorage - ->expects(self::once()) - ->method('hasToken') - ->with('https-' . self::CSRF_TOKEN_INTENTION); - - $csrfTokenManager->hasToken(self::CSRF_TOKEN_INTENTION); - } - - private function createCsrfTokenManager($https = false) - { - $request = new Request(); - if ($https) { - $request->server->set('HTTPS', 'ON'); - } - - $this->requestStack - ->expects(self::once()) - ->method('getMainRequest') - ->willReturn($request); - - return new CsrfTokenManager( - $this->createMock(TokenGeneratorInterface::class), - $this->tokenStorage, - $this->requestStack - ); - } -} diff --git a/tests/lib/Server/Security/RestLogoutHandlerTest.php b/tests/lib/Server/Security/RestLogoutHandlerTest.php deleted file mode 100644 index 3b8516b2..00000000 --- a/tests/lib/Server/Security/RestLogoutHandlerTest.php +++ /dev/null @@ -1,123 +0,0 @@ -configResolver = $this->createMock(ConfigResolverInterface::class); - $this->session = $this->createMock(SessionInterface::class); - } - - public function testLogoutWithoutSiteaccessSessionSettings() - { - $sessionId = 'eZSESSID'; - $this->session - ->expects(self::once()) - ->method('getName') - ->willReturn($sessionId); - $request = new Request(); - $request->setSession($this->session); - $request->attributes->set('is_rest_request', true); - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('session') - ->willReturn([]); - $response = new Response(); - $response->headers = $this->createMock(ResponseHeaderBag::class); - $response->headers - ->expects(self::once()) - ->method('clearCookie') - ->with($sessionId); - $logoutHandler = new RestLogoutHandler($this->configResolver); - $logoutHandler->logout( - $request, - $response, - $this->createMock(TokenInterface::class) - ); - } - - public function testLogoutWithSiteaccessSessionSettings() - { - $sessionId = 'eZSESSID'; - $this->session - ->expects(self::once()) - ->method('getName') - ->willReturn($sessionId); - $request = new Request(); - $request->setSession($this->session); - $request->attributes->set('is_rest_request', true); - $sessionSettings = [ - 'cookie_path' => '/', - 'cookie_domain' => 'ibexa.co', - ]; - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('session') - ->willReturn($sessionSettings); - $response = new Response(); - $response->headers = $this->createMock(ResponseHeaderBag::class); - $response->headers - ->expects(self::once()) - ->method('clearCookie') - ->with($sessionId, $sessionSettings['cookie_path'], $sessionSettings['cookie_domain']); - $logoutHandler = new RestLogoutHandler($this->configResolver); - $logoutHandler->logout( - $request, - $response, - $this->createMock(TokenInterface::class) - ); - } - - public function testLogoutNotRest() - { - $session = $this->createMock(SessionInterface::class); - $session - ->expects(self::never()) - ->method('getName'); - - $request = new Request(); - $request->setSession($session); - - $response = new Response(); - $response->headers = $this->createMock(ResponseHeaderBag::class); - $response->headers - ->expects(self::never()) - ->method('clearCookie'); - - $logoutHandler = new RestLogoutHandler($this->configResolver); - $logoutHandler->logout( - $request, - $response, - $this->createMock(TokenInterface::class) - ); - } -} diff --git a/tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php b/tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php deleted file mode 100644 index 4a38becc..00000000 --- a/tests/lib/Server/Security/RestSessionBasedAuthenticatorTest.php +++ /dev/null @@ -1,572 +0,0 @@ -tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->authenticationManager = $this->createMock(AuthenticationManagerInterface::class); - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - $this->configResolver = $this->createMock(ConfigResolverInterface::class); - $this->logger = $this->createMock(LoggerInterface::class); - $this->authenticator = new RestAuthenticator( - $this->tokenStorage, - $this->authenticationManager, - self::PROVIDER_KEY, - $this->eventDispatcher, - $this->configResolver, - $this->logger - ); - } - - public function testAuthenticateAlreadyHaveSessionToken() - { - $username = 'foo_user'; - $password = 'publish'; - - $existingToken = $this->getTokenInterfaceMock(); - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->willReturn($existingToken); - - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn($username); - $existingToken - ->expects(self::once()) - ->method('setAttribute') - ->with('isFromSession', true); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - self::assertSame($existingToken, $this->authenticator->authenticate($request)); - } - - public function testAuthenticateNoTokenFound() - { - $this->expectException(TokenNotFoundException::class); - $username = 'foo_user'; - $password = 'publish'; - - $existingToken = $this->getTokenInterfaceMock(); - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->willReturn($existingToken); - - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn(null); - - $this->logger - ->expects(self::once()) - ->method('error'); - - $this->authenticator->authenticate($request); - } - - public function testAuthenticateInvalidUser() - { - $this->expectException(InvalidUserTypeException::class); - $username = 'foo_user'; - $password = 'publish'; - - $existingToken = $this->getTokenInterfaceMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->tokenStorage - ->expects(self::once()) - ->method('setToken') - ->with($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::exactly(2)) - ->method('getToken') - ->will( - self::onConsecutiveCalls($existingToken, $authenticatedToken) - ); - - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn('not_an_ibexa_user'); - - $this->logger - ->expects(self::once()) - ->method('error'); - - $this->authenticator->authenticate($request); - } - - /** - * @param $userId - * - * @return \Ibexa\Core\MVC\Symfony\Security\User - */ - private function createUser($userId) - { - $apiUser = $this->createMock(User::class); - $apiUser - ->expects(self::any()) - ->method('getUserId') - ->willReturn($userId); - - return new IbexaUser($apiUser); - } - - public function testAuthenticateUserConflict() - { - $this->expectException(UserConflictException::class); - $username = 'foo_user'; - $password = 'publish'; - - $existingUser = $this->createUser(123); - $existingToken = $this->getUsernamePasswordTokenMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - $existingToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($existingUser); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::at(0)) - ->method('getToken') - ->willReturn($existingToken); - $this->tokenStorage - ->expects(self::at(1)) - ->method('setToken') - ->with($authenticatedToken); - $this->tokenStorage - ->expects(self::at(2)) - ->method('getToken') - ->willReturn($authenticatedToken); - $this->tokenStorage - ->expects(self::at(3)) - ->method('setToken') - ->with($existingToken); - - $authenticatedUser = $this->createUser(456); - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($authenticatedUser); - - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('anonymous_user_id') - ->willReturn(10); - - $this->authenticator->authenticate($request); - } - - public function testAuthenticatePreviouslyAnonymous() - { - $username = 'foo_user'; - $password = 'publish'; - - $anonymousUserId = 10; - $existingUser = $this->createUser($anonymousUserId); - $existingToken = $this->getUsernamePasswordTokenMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - $existingToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($existingUser); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::at(0)) - ->method('getToken') - ->willReturn($existingToken); - $this->tokenStorage - ->expects(self::at(1)) - ->method('setToken') - ->with($authenticatedToken); - $this->tokenStorage - ->expects(self::at(2)) - ->method('getToken') - ->willReturn($authenticatedToken); - - $authenticatedUser = $this->createUser(456); - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($authenticatedUser); - - $this->configResolver - ->expects(self::once()) - ->method('getParameter') - ->with('anonymous_user_id') - ->willReturn($anonymousUserId); - - self::assertSame($authenticatedToken, $this->authenticator->authenticate($request)); - } - - public function testAuthenticate() - { - $username = 'foo_user'; - $password = 'publish'; - - $existingToken = $this->getTokenInterfaceMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::at(0)) - ->method('getToken') - ->willReturn($existingToken); - $this->tokenStorage - ->expects(self::at(1)) - ->method('setToken') - ->with($authenticatedToken); - $this->tokenStorage - ->expects(self::at(2)) - ->method('getToken') - ->willReturn($authenticatedToken); - - $authenticatedUser = $this->createUser(456); - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($authenticatedUser); - - self::assertSame($authenticatedToken, $this->authenticator->authenticate($request)); - } - - public function testAuthenticatePreviousUserNonEz() - { - $username = 'foo_user'; - $password = 'publish'; - - $existingUser = $this->createMock(UserInterface::class); - $existingToken = $this->getUsernamePasswordTokenMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - $existingToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($existingUser); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::at(0)) - ->method('getToken') - ->willReturn($existingToken); - $this->tokenStorage - ->expects(self::at(1)) - ->method('setToken') - ->with($authenticatedToken); - $this->tokenStorage - ->expects(self::at(2)) - ->method('getToken') - ->willReturn($authenticatedToken); - - $authenticatedUser = $this->createUser(456); - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($authenticatedUser); - - self::assertSame($authenticatedToken, $this->authenticator->authenticate($request)); - } - - public function testAuthenticatePreviousTokenNotUsernamePassword() - { - $username = 'foo_user'; - $password = 'publish'; - - $existingToken = $this->getTokenInterfaceMock(); - $existingToken - ->expects(self::once()) - ->method('getUsername') - ->willReturn(__METHOD__); - - $request = new Request(); - $request->attributes->set('username', $username); - $request->attributes->set('password', $password); - - $usernamePasswordToken = new UsernamePasswordToken($username, $password, self::PROVIDER_KEY); - $authenticatedToken = $this->getUsernamePasswordTokenMock(); - $this->authenticationManager - ->expects(self::once()) - ->method('authenticate') - ->with(self::equalTo($usernamePasswordToken)) - ->willReturn($authenticatedToken); - - $this->eventDispatcher - ->expects(self::once()) - ->method('dispatch') - ->with( - self::equalTo(new InteractiveLoginEvent($request, $authenticatedToken)), - SecurityEvents::INTERACTIVE_LOGIN - ); - - $this->tokenStorage - ->expects(self::at(0)) - ->method('getToken') - ->willReturn($existingToken); - $this->tokenStorage - ->expects(self::at(1)) - ->method('setToken') - ->with($authenticatedToken); - $this->tokenStorage - ->expects(self::at(2)) - ->method('getToken') - ->willReturn($authenticatedToken); - - $authenticatedUser = $this->createUser(456); - $authenticatedToken - ->expects(self::once()) - ->method('getUser') - ->willReturn($authenticatedUser); - - self::assertSame($authenticatedToken, $this->authenticator->authenticate($request)); - } - - public function testLogout() - { - $sessionLogoutHandler = $this->createMock(SessionLogoutHandler::class); - $sessionLogoutHandler - ->expects(self::never()) - ->method('logout'); - - $token = $this->getTokenInterfaceMock(); - $this->tokenStorage - ->expects(self::once()) - ->method('getToken') - ->willReturn($token); - - $request = new Request(); - $request->setSession(new Session(new MockArraySessionStorage())); - - $logoutHandler1 = $this->createMock(LogoutHandlerInterface::class); - $logoutHandler1 - ->expects(self::once()) - ->method('logout') - ->with( - $request, - self::isInstanceOf(Response::class), - $token - ); - $logoutHandler2 = $this->createMock(LogoutHandlerInterface::class); - $logoutHandler2 - ->expects(self::once()) - ->method('logout') - ->with( - $request, - self::isInstanceOf(Response::class), - $token - ); - - $this->authenticator->addLogoutHandler($sessionLogoutHandler); - $this->authenticator->addLogoutHandler($logoutHandler1); - $this->authenticator->addLogoutHandler($logoutHandler2); - - self::assertInstanceOf( - Response::class, - $this->authenticator->logout($request) - ); - } - - protected function getTokenInterfaceMock() - { - return $this->createMock(TokenInterface::class); - } - - protected function getUsernamePasswordTokenMock() - { - return $this->createMock(UsernamePasswordToken::class); - } -}