From 2e83edbee682ab43ed808d143e822b4b31d2a3c3 Mon Sep 17 00:00:00 2001 From: r3h6 Date: Fri, 14 Jun 2024 22:33:03 +0200 Subject: [PATCH] [TASK] Refactor for PHPStan level 8 and more --- .ddev/config.yaml | 157 ++++--- .github/workflows/ci.yml | 4 +- .gitignore | 2 + Build/Docker/docker-compose.yml | 1 - Build/setup.sh | 2 +- Classes/Configuration/Configuration.php | 5 +- .../Controller/AuthorizationController.php | 5 +- Classes/Controller/ConsentController.php | 23 +- Classes/Controller/RevokeController.php | 14 +- Classes/Controller/TokenController.php | 10 +- .../Domain/Bridge/AccessTokenRepository.php | 23 +- Classes/Domain/Bridge/AuthCodeRepository.php | 23 +- .../Domain/Bridge/RefreshTokenRepository.php | 23 +- Classes/Domain/Bridge/RequestEvent.php | 12 +- Classes/Domain/Model/AccessToken.php | 41 +- Classes/Domain/Model/AuthCode.php | 51 +-- Classes/Domain/Model/Client.php | 132 ++---- Classes/Domain/Model/FrontendUser.php | 412 +++--------------- Classes/Domain/Model/FrontendUserGroup.php | 96 +--- Classes/Domain/Model/RefreshToken.php | 28 +- Classes/Domain/Model/Scope.php | 15 +- .../Repository/AccessTokenRepository.php | 8 +- .../Domain/Repository/AuthCodeRepository.php | 6 +- .../Domain/Repository/ClientRepository.php | 14 +- .../Repository/RefreshTokenRepository.php | 11 +- Classes/Domain/Repository/ScopeRepository.php | 18 +- Classes/Domain/Repository/UserRepository.php | 13 +- Classes/Middleware/Dispatcher.php | 10 +- Classes/Middleware/Initializer.php | 13 +- .../Mvc/Controller/AuthorizationContext.php | 14 +- Classes/Service/Oauth2AuthService.php | 2 +- Classes/Session/Session.php | 17 +- Classes/Utility/ScopeUtility.php | 3 + Configuration/Routing/config.yaml | 2 +- Configuration/Services.yaml | 11 - .../TCA/Overrides/sys_template.php | 0 ..._oauth2server_domain_model_accesstoken.php | 4 +- .../tx_oauth2server_domain_model_authcode.php | 4 +- .../tx_oauth2server_domain_model_client.php | 33 +- ...oauth2server_domain_model_refreshtoken.php | 4 +- Tests/Fixtures/Database/base.csv | 17 + Tests/Fixtures/config/sites/main/config.yaml | 17 + Tests/Functional/AuthorizationServerTest.php | 104 +++++ .../Repository/ClientRepositoryTest.php | 52 +++ composer.json | 27 +- ext_emconf.php | 4 +- phpmd-rulset.xml | 23 - phpstan.neon | 31 +- rector.php | 4 +- 49 files changed, 651 insertions(+), 894 deletions(-) rename ext_tables.php => Configuration/TCA/Overrides/sys_template.php (100%) create mode 100644 Tests/Fixtures/Database/base.csv create mode 100644 Tests/Fixtures/config/sites/main/config.yaml create mode 100644 Tests/Functional/AuthorizationServerTest.php create mode 100644 Tests/Unit/Domain/Repository/ClientRepositoryTest.php delete mode 100644 phpmd-rulset.xml diff --git a/.ddev/config.yaml b/.ddev/config.yaml index fdd8dde..2aefb9a 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,7 +1,7 @@ name: typo3-oauth2-server type: typo3 docroot: .Build/public/ -php_version: "8.1" +php_version: "8.2" webserver_type: nginx-fpm router_http_port: "80" router_https_port: "443" @@ -9,53 +9,56 @@ xdebug_enabled: false additional_hostnames: [] additional_fqdns: [] database: - type: mariadb - version: "10.4" -nfs_mount_enabled: false -mutagen_enabled: false + type: mariadb + version: "10.4" use_dns_when_possible: true composer_version: "2" web_environment: - - TYPO3_CONTEXT=Development + - TYPO3_CONTEXT=Development + - typo3DatabaseDriver=pdo_sqlite nodejs_version: "16" +corepack_enable: false -# Key features of ddev's config.yaml: +# Key features of DDEV's config.yaml: # name: # Name of the project, automatically provides # http://projectname.ddev.site and https://projectname.ddev.site -# type: # drupal6/7/8, backdrop, typo3, wordpress, php +# type: # backdrop, craftcms, django4, drupal, drupal6, drupal7, laravel, magento, magento2, php, python, shopware6, silverstripe, typo3, wordpress +# See https://ddev.readthedocs.io/en/stable/users/quickstart/ for more +# information on the different project types +# "drupal" covers recent Drupal 8+ # docroot: # Relative path to the directory containing index.php. -# php_version: "7.4" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2" +# php_version: "8.2" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3" # You can explicitly specify the webimage but this -# is not recommended, as the images are often closely tied to ddev's' behavior, +# is not recommended, as the images are often closely tied to DDEV's' behavior, # so this can break upgrades. # webimage: # nginx/php docker image. # database: -# type: # mysql, mariadb -# version: # database version, like "10.3" or "8.0" -# Note that mariadb_version or mysql_version from v1.18 and earlier -# will automatically be converted to this notation with just a "ddev config --auto" +# type: # mysql, mariadb, postgres +# version: # database version, like "10.11" or "8.0" +# MariaDB versions can be 5.5-10.8 and 10.11, MySQL versions can be 5.5-8.0 +# PostgreSQL versions can be 9-16. -# router_http_port: # Port to be used for http (defaults to port 80) -# router_https_port: # Port for https (defaults to 443) +# router_http_port: # Port to be used for http (defaults to global configuration, usually 80) +# router_https_port: # Port for https (defaults to global configuration, usually 443) -# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart" +# xdebug_enabled: false # Set to true to enable Xdebug and "ddev start" or "ddev restart" # Note that for most people the commands -# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better, -# as leaving xdebug enabled all the time is a big performance hit. +# "ddev xdebug" to enable Xdebug and "ddev xdebug off" to disable it work better, +# as leaving Xdebug enabled all the time is a big performance hit. -# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart" +# xhprof_enabled: false # Set to true to enable Xhprof and "ddev start" or "ddev restart" # Note that for most people the commands -# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better, -# as leaving xhprof enabled all the time is a big performance hit. +# "ddev xhprof" to enable Xhprof and "ddev xhprof off" to disable it work better, +# as leaving Xhprof enabled all the time is a big performance hit. -# webserver_type: nginx-fpm # or apache-fpm +# webserver_type: nginx-fpm, apache-fpm, or nginx-gunicorn # timezone: Europe/Berlin # This is the timezone used in the containers and by PHP; @@ -64,7 +67,7 @@ nodejs_version: "16" # For example Europe/Dublin or MST7MDT # composer_root: -# Relative path to the composer root directory from the project root. This is +# Relative path to the Composer root directory from the project root. This is # the directory which contains the composer.json and where all Composer related # commands are executed. @@ -79,10 +82,17 @@ nodejs_version: "16" # Alternatively, an explicit Composer version may be specified, for example "2.2.18". # To reinstall Composer after the image was built, run "ddev debug refresh". -# nodejs_version: "16" -# change from the default system Node.js version to another supported version, like 12, 14, 17, 18. -# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any -# Node.js version, including v6, etc. +# nodejs_version: "20" +# change from the default system Node.js version to any other version. +# Numeric version numbers can be complete (i.e. 18.15.0) or +# incomplete (18, 17.2, 16). 'lts' and 'latest' can be used as well along with +# other named releases. +# see https://www.npmjs.com/package/n#specifying-nodejs-versions +# Note that you can continue using 'ddev nvm' or nvm inside the web container +# to change the project's installed node version if you need to. + +# corepack_enable: false +# Change to 'true' to 'corepack enable' and gain access to latest versions of yarn/pnpm # additional_hostnames: # - somename @@ -96,10 +106,26 @@ nodejs_version: "16" # would provide http and https URLs for "example.com" and "sub1.example.com" # Please take care with this because it can cause great confusion. -# upload_dir: custom/upload/dir -# would set the destination path for ddev import-files to /custom/upload/dir -# When mutagen is enabled this path is bind-mounted so that all the files -# in the upload_dir don't have to be synced into mutagen +# upload_dirs: "custom/upload/dir" +# +# upload_dirs: +# - custom/upload/dir +# - ../private +# +# would set the destination paths for ddev import-files to /custom/upload/dir +# When Mutagen is enabled this path is bind-mounted so that all the files +# in the upload_dirs don't have to be synced into Mutagen. + +# disable_upload_dirs_warning: false +# If true, turns off the normal warning that says +# "You have Mutagen enabled and your 'php' project type doesn't have upload_dirs set" + +# ddev_version_constraint: "" +# Example: +# ddev_version_constraint: ">= 1.22.4" +# This will enforce that the running ddev version is within this constraint. +# See https://github.com/Masterminds/semver#checking-version-constraints for +# supported constraint formats # working_dir: # web: /var/www/html @@ -108,20 +134,25 @@ nodejs_version: "16" # These values specify the destination directory for ddev ssh and the # directory in which commands passed into ddev exec are run. -# omit_containers: [db, dba, ddev-ssh-agent] +# omit_containers: [db, ddev-ssh-agent] # Currently only these containers are supported. Some containers can also be # omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit -# the "db" container, several standard features of ddev that access the +# the "db" container, several standard features of DDEV that access the # database container will be unusable. In the global configuration it is also # possible to omit ddev-router, but not here. -# nfs_mount_enabled: false -# Great performance improvement but requires host configuration first. -# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs - -# mutagen_enabled: false -# Performance improvement using mutagen asynchronous updates. -# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen +# performance_mode: "global" +# DDEV offers performance optimization strategies to improve the filesystem +# performance depending on your host system. Should be configured globally. +# +# If set, will override the global config. Possible values are: +# - "global": uses the value from the global config. +# - "none": disables performance optimization for this project. +# - "mutagen": enables Mutagen for this project. +# - "nfs": enables NFS for this project. +# +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#nfs +# See https://ddev.readthedocs.io/en/stable/users/install/performance/#mutagen # fail_on_hook_fail: False # Decide whether 'ddev start' should be interrupted by a failing hook @@ -142,20 +173,12 @@ nodejs_version: "16" # The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic # unless explicitly specified. -# phpmyadmin_port: "8036" -# phpmyadmin_https_port: "8037" -# The PHPMyAdmin ports can be changed from the default 8036 and 8037 - -# host_phpmyadmin_port: "8036" -# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed -# through ddev-router, but it can be specified and bound. - -# mailhog_port: "8025" -# mailhog_https_port: "8026" -# The MailHog ports can be changed from the default 8025 and 8026 +# mailpit_http_port: "8025" +# mailpit_https_port: "8026" +# The Mailpit ports can be changed from the default 8025 and 8026 -# host_mailhog_port: "8025" -# The mailhog port is not normally bound on the host at all, instead being routed +# host_mailpit_port: "8025" +# The mailpit port is not normally bound on the host at all, instead being routed # through ddev-router, but it can be bound directly to localhost if specified here. # webimage_extra_packages: [php7.4-tidy, php-bcmath] @@ -178,10 +201,10 @@ nodejs_version: "16" # ngrok_args: --basic-auth username:pass1234 # Provide extra flags to the "ngrok http" command, see -# https://ngrok.com/docs#http or run "ngrok http -h" +# https://ngrok.com/docs/ngrok-agent/config or run "ngrok http -h" # disable_settings_management: false -# If true, ddev will not create CMS-specific settings files like +# If true, DDEV will not create CMS-specific settings files like # Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php # In this case the user must provide all such settings. @@ -191,19 +214,19 @@ nodejs_version: "16" # - SOMEOTHERENV=someothervalue # no_project_mount: false -# (Experimental) If true, ddev will not mount the project into the web container; +# (Experimental) If true, DDEV will not mount the project into the web container; # the user is responsible for mounting it manually or via a script. # This is to enable experimentation with alternate file mounting strategies. # For advanced users only! # bind_all_interfaces: false # If true, host ports will be bound on all network interfaces, -# not just the localhost interface. This means that ports +# not the localhost interface only. This means that ports # will be available on the local network if the host firewall # allows it. # default_container_timeout: 120 -# The default time that ddev waits for all containers to become ready can be increased from +# The default time that DDEV waits for all containers to become ready can be increased from # the default 120. This helps in importing huge databases, for example. #web_extra_exposed_ports: @@ -216,12 +239,16 @@ nodejs_version: "16" # https_port: 4000 # http_port: 3999 # Allows a set of extra ports to be exposed via ddev-router +# Fill in all three fields even if you don’t intend to use the https_port! +# If you don’t add https_port, then it defaults to 0 and ddev-router will fail to start. +# # The port behavior on the ddev-webserver must be arranged separately, for example # using web_extra_daemons. # For example, with a web app on port 3000 inside the container, this config would # expose that web app on https://.ddev.site:9999 and http://.ddev.site:9998 # web_extra_exposed_ports: -# - container_port: 3000 +# - name: myapp +# container_port: 3000 # http_port: 9998 # https_port: 9999 @@ -236,10 +263,10 @@ nodejs_version: "16" # override_config: false # By default, config.*.yaml files are *merged* into the configuration # But this means that some things can't be overridden -# For example, if you have 'nfs_mount_enabled: true'' you can't override it with a merge +# For example, if you have 'use_dns_when_possible: true'' you can't override it with a merge # and you can't erase existing hooks or all environment variables. # However, with "override_config: true" in a particular config.*.yaml file, -# 'nfs_mount_enabled: false' can override the existing values, and +# 'use_dns_when_possible: false' can override the existing values, and # hooks: # post-start: [] # or @@ -249,10 +276,12 @@ nodejs_version: "16" # can have their intended affect. 'override_config' affects only behavior of the # config.*.yaml file it exists in. -# Many ddev commands can be extended to run tasks before or after the -# ddev command is executed, for example "post-start", "post-import-db", +# Many DDEV commands can be extended to run tasks before or after the +# DDEV command is executed, for example "post-start", "post-import-db", # "pre-composer", "post-composer" # See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more # information on the commands that can be extended and the tasks you can define # for them. Example: #hooks: +# post-start: +# - exec: composer install -d /var/www/html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a7cda7..389e631 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,8 @@ jobs: - { PHP: '8.1', TYPO3_VERSION: ^12.4 } - { PHP: '8.2', TYPO3_VERSION: ^12.4 } - { PHP: '8.3', TYPO3_VERSION: ^12.4 } - - { PHP: '8.2', TYPO3_VERSION: ^13.1 } - - { PHP: '8.3', TYPO3_VERSION: ^13.1 } + # - { PHP: '8.2', TYPO3_VERSION: ^13.1 } + # - { PHP: '8.3', TYPO3_VERSION: ^13.1 } env: ${{ matrix.env }} diff --git a/.gitignore b/.gitignore index def44e1..4108006 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules *.cache /var +.ddev/*adminer* +.ddev/addon-metadata diff --git a/Build/Docker/docker-compose.yml b/Build/Docker/docker-compose.yml index 7658b67..fe94568 100644 --- a/Build/Docker/docker-compose.yml +++ b/Build/Docker/docker-compose.yml @@ -1,4 +1,3 @@ -version: '2.3' services: composer: diff --git a/Build/setup.sh b/Build/setup.sh index f096f22..1d0cf1d 100755 --- a/Build/setup.sh +++ b/Build/setup.sh @@ -6,7 +6,7 @@ rm -rf var rm composer.lock ddev start -ddev import-db --src=Build/Data/db.sql +ddev import-db --file=Build/Data/db.sql ddev composer install mkdir -p .Build/public/fileadmin ddev launch typo3 diff --git a/Classes/Configuration/Configuration.php b/Classes/Configuration/Configuration.php index 8002e65..271fbe5 100644 --- a/Classes/Configuration/Configuration.php +++ b/Classes/Configuration/Configuration.php @@ -18,6 +18,9 @@ * ***/ +/** + * @implements \ArrayAccess + */ class Configuration implements \ArrayAccess, SingletonInterface { private array $configuration = [ @@ -111,6 +114,6 @@ public function getScopes(): array public function merge(array $overrideConfiguration): void { - ArrayUtility::mergeRecursiveWithOverrule($this->configuration, $overrideConfiguration, true, true, false); + ArrayUtility::mergeRecursiveWithOverrule($this->configuration, $overrideConfiguration, true, false, false); } } diff --git a/Classes/Controller/AuthorizationController.php b/Classes/Controller/AuthorizationController.php index 83df936..a6e3b98 100644 --- a/Classes/Controller/AuthorizationController.php +++ b/Classes/Controller/AuthorizationController.php @@ -142,7 +142,10 @@ protected function finishAuthorization(AuthorizationContext $context, bool $appr protected function setUserToAuthorizationRequest(AuthorizationContext $context): void { - $user = $this->userRepository->findByUid($context->getFrontendUserUid()); + $user = $this->userRepository->findByUid((int)$context->getFrontendUserUid()); + if ($user === null) { + throw new \RuntimeException('User not found', 1718223076476); + } $this->logger->debug('Set user to authorization request', ['user' => $user]); $context->getAuthRequest()->setUser($user); } diff --git a/Classes/Controller/ConsentController.php b/Classes/Controller/ConsentController.php index 777ecd5..1a524cc 100644 --- a/Classes/Controller/ConsentController.php +++ b/Classes/Controller/ConsentController.php @@ -28,15 +28,10 @@ class ConsentController extends ActionController { - /** - * @var Context - */ - protected $context; - - /** - * @var Configuration - */ - protected $configuration; + public function __construct( + protected Context $context, + protected Configuration $configuration + ) {} public function showAction(): ResponseInterface { @@ -72,14 +67,4 @@ protected function getAuthRequestOrFail(): AuthorizationRequest } return $authRequest; } - - public function injectContext(Context $context): void - { - $this->context = $context; - } - - public function injectConfiguration(Configuration $configuration): void - { - $this->configuration = $configuration; - } } diff --git a/Classes/Controller/RevokeController.php b/Classes/Controller/RevokeController.php index a53981c..c25e978 100644 --- a/Classes/Controller/RevokeController.php +++ b/Classes/Controller/RevokeController.php @@ -22,19 +22,13 @@ class RevokeController { - /** - * @var AccessTokenRepositoryInterface - */ - protected $accessTokenRepository; - - public function __construct(AccessTokenRepositoryInterface $accessTokenRepository) - { - $this->accessTokenRepository = $accessTokenRepository; - } + public function __construct( + protected AccessTokenRepositoryInterface $accessTokenRepository + ) {} public function revokeAccessToken(ServerRequestInterface $request): ResponseInterface { - $tokenId = $request->getAttribute('oauth_access_token_id'); + $tokenId = (string)$request->getAttribute('oauth_access_token_id'); $this->accessTokenRepository->revokeAccessToken($tokenId); return new Response('', 204); } diff --git a/Classes/Controller/TokenController.php b/Classes/Controller/TokenController.php index ceeb388..2bc06cb 100644 --- a/Classes/Controller/TokenController.php +++ b/Classes/Controller/TokenController.php @@ -22,15 +22,7 @@ class TokenController { - /** - * @var AuthorizationServer - */ - private $server; - - public function __construct(AuthorizationServer $server) - { - $this->server = $server; - } + public function __construct(private AuthorizationServer $server) {} public function issueAccessToken(ServerRequestInterface $request): ResponseInterface { diff --git a/Classes/Domain/Bridge/AccessTokenRepository.php b/Classes/Domain/Bridge/AccessTokenRepository.php index a158e53..7a93143 100644 --- a/Classes/Domain/Bridge/AccessTokenRepository.php +++ b/Classes/Domain/Bridge/AccessTokenRepository.php @@ -28,15 +28,7 @@ class AccessTokenRepository implements SingletonInterface, AccessTokenRepository { use LoggerAwareTrait; - /** - * @var \R3H6\Oauth2Server\Domain\Repository\AccessTokenRepository - */ - private $repository; - - public function __construct(\R3H6\Oauth2Server\Domain\Repository\AccessTokenRepository $repository) - { - $this->repository = $repository; - } + public function __construct(private \R3H6\Oauth2Server\Domain\Repository\AccessTokenRepository $repository) {} public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) { @@ -54,7 +46,7 @@ public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, return $accessToken; } - public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity) + public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void { $newToken = GeneralUtility::makeInstance(\R3H6\Oauth2Server\Domain\Model\AccessToken::class); $newToken->setPid(0); @@ -67,10 +59,14 @@ public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEnt $this->repository->persist(); } - public function revokeAccessToken($tokenId) + public function revokeAccessToken($tokenId): void { $this->logger->debug('Revoke access token', ['identifier' => $tokenId]); - $token = $this->repository->findOneByIdentifier($tokenId); + $token = $this->repository->findOneBy(['identifier' => $tokenId]); + if (!$token) { + $this->logger->warning('Access token not found', ['identifier' => $tokenId]); + return; + } $token->setRevoked(true); $this->repository->update($token); $this->repository->persist(); @@ -78,10 +74,11 @@ public function revokeAccessToken($tokenId) public function isAccessTokenRevoked($tokenId) { - $token = $this->repository->findOneByIdentifier($tokenId); + $token = $this->repository->findOneBy(['identifier' => $tokenId]); if ($token) { return $token->getRevoked(); } + $this->logger->warning('Access token not found', ['identifier' => $tokenId]); return true; } } diff --git a/Classes/Domain/Bridge/AuthCodeRepository.php b/Classes/Domain/Bridge/AuthCodeRepository.php index 7e17329..20aa5da 100644 --- a/Classes/Domain/Bridge/AuthCodeRepository.php +++ b/Classes/Domain/Bridge/AuthCodeRepository.php @@ -27,15 +27,7 @@ class AuthCodeRepository implements SingletonInterface, AuthCodeRepositoryInterf { use LoggerAwareTrait; - /** - * @var \R3H6\Oauth2Server\Domain\Repository\AuthCodeRepository - */ - private $repository; - - public function __construct(\R3H6\Oauth2Server\Domain\Repository\AuthCodeRepository $repository) - { - $this->repository = $repository; - } + public function __construct(private \R3H6\Oauth2Server\Domain\Repository\AuthCodeRepository $repository) {} public function getNewAuthCode() { @@ -44,7 +36,7 @@ public function getNewAuthCode() return new AuthCode(); } - public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) + public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void { $newToken = GeneralUtility::makeInstance(\R3H6\Oauth2Server\Domain\Model\AuthCode::class); $newToken->setIdentifier($authCodeEntity->getIdentifier()); @@ -56,10 +48,14 @@ public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) $this->repository->persist(); } - public function revokeAuthCode($codeId) + public function revokeAuthCode($codeId): void { $this->logger->debug('Revoke auth code', ['identifier' => $codeId]); - $token = $this->repository->findOneByIdentifier($codeId); + $token = $this->repository->findOneBy(['identifier' => $codeId]); + if (!$token) { + $this->logger->warning('Auth code not found', ['identifier' => $codeId]); + return; + } $token->setRevoked(true); $this->repository->update($token); $this->repository->persist(); @@ -67,10 +63,11 @@ public function revokeAuthCode($codeId) public function isAuthCodeRevoked($codeId) { - $token = $this->repository->findOneByIdentifier($codeId); + $token = $this->repository->findOneBy(['identifier' => $codeId]); if ($token) { return $token->getRevoked(); } + $this->logger->warning('Auth code not found', ['identifier' => $codeId]); return true; } } diff --git a/Classes/Domain/Bridge/RefreshTokenRepository.php b/Classes/Domain/Bridge/RefreshTokenRepository.php index ad090ae..92e9ed5 100644 --- a/Classes/Domain/Bridge/RefreshTokenRepository.php +++ b/Classes/Domain/Bridge/RefreshTokenRepository.php @@ -26,15 +26,7 @@ class RefreshTokenRepository implements SingletonInterface, RefreshTokenReposito { use LoggerAwareTrait; - /** - * @var \R3H6\Oauth2Server\Domain\Repository\RefreshTokenRepository - */ - private $repository; - - public function __construct(\R3H6\Oauth2Server\Domain\Repository\RefreshTokenRepository $repository) - { - $this->repository = $repository; - } + public function __construct(private \R3H6\Oauth2Server\Domain\Repository\RefreshTokenRepository $repository) {} public function getNewRefreshToken() { @@ -43,7 +35,7 @@ public function getNewRefreshToken() return new RefreshToken(); } - public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity) + public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void { $newToken = GeneralUtility::makeInstance(\R3H6\Oauth2Server\Domain\Model\RefreshToken::class); $newToken->setIdentifier($refreshTokenEntity->getIdentifier()); @@ -53,10 +45,14 @@ public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshToken $this->repository->persist(); } - public function revokeRefreshToken($tokenId) + public function revokeRefreshToken($tokenId): void { $this->logger->debug('Revoke refresh token', ['identifier' => $tokenId]); - $token = $this->repository->findOneByIdentifier($tokenId); + $token = $this->repository->findOneBy(['identifier' => $tokenId]); + if (!$token) { + $this->logger->warning('Refresh token not found', ['identifier' => $tokenId]); + return; + } $token->setRevoked(true); $this->repository->update($token); $this->repository->persist(); @@ -64,10 +60,11 @@ public function revokeRefreshToken($tokenId) public function isRefreshTokenRevoked($tokenId) { - $token = $this->repository->findOneByIdentifier($tokenId); + $token = $this->repository->findOneBy(['identifier' => $tokenId]); if ($token) { return $token->getRevoked(); } + $this->logger->warning('Refresh token not found', ['identifier' => $tokenId]); return true; } } diff --git a/Classes/Domain/Bridge/RequestEvent.php b/Classes/Domain/Bridge/RequestEvent.php index abc7904..110c778 100644 --- a/Classes/Domain/Bridge/RequestEvent.php +++ b/Classes/Domain/Bridge/RequestEvent.php @@ -27,17 +27,9 @@ final class RequestEvent implements LoggerAwareInterface { use LoggerAwareTrait; - /** - * @var EventDispatcher - */ - private $eventDispatcher; + public function __construct(private EventDispatcher $eventDispatcher) {} - public function __construct(EventDispatcher $eventDispatcher) - { - $this->eventDispatcher = $eventDispatcher; - } - - public function __invoke(OAuth2RequestEvent $event) + public function __invoke(OAuth2RequestEvent $event): void { $this->logger->debug('Forward event', ['name' => $event->getName()]); $this->eventDispatcher->dispatch($event); diff --git a/Classes/Domain/Model/AccessToken.php b/Classes/Domain/Model/AccessToken.php index 74d8b0c..d2a3235 100644 --- a/Classes/Domain/Model/AccessToken.php +++ b/Classes/Domain/Model/AccessToken.php @@ -17,25 +17,14 @@ class AccessToken extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity { - /** @var string */ - protected $identifier = ''; - - /** @var \DateTimeImmutable */ - protected $expiresAt; - - /** @var string */ - protected $user = ''; - - /** @var string */ - protected $scopes = ''; - - /** @var string */ - protected $client = ''; - - /** @var bool */ - protected $revoked = false; - - public function setIdentifier(string $identifier) + protected string $identifier = ''; + protected ?\DateTimeImmutable $expiresAt = null; + protected string $user = ''; + protected string $scopes = ''; + protected string $client = ''; + protected bool $revoked = false; + + public function setIdentifier(string $identifier): void { $this->identifier = $identifier; } @@ -45,17 +34,17 @@ public function getIdentifier(): string return $this->identifier; } - public function setExpiresAt(\DateTimeImmutable $expiresAt) + public function setExpiresAt(?\DateTimeImmutable $expiresAt): void { $this->expiresAt = $expiresAt; } - public function getExpiresAt(): \DateTimeImmutable + public function getExpiresAt(): ?\DateTimeImmutable { return $this->expiresAt; } - public function setUser(string $user) + public function setUser(string $user): void { $this->user = $user; } @@ -65,7 +54,7 @@ public function getUser(): string return $this->user; } - public function setScopes(string $scopes) + public function setScopes(string $scopes): void { $this->scopes = $scopes; } @@ -75,7 +64,7 @@ public function getScopes(): string return $this->scopes; } - public function setClient(string $client) + public function setClient(string $client): void { $this->client = $client; } @@ -85,12 +74,12 @@ public function getClient(): string return $this->client; } - public function setRevoked($revoked) + public function setRevoked(bool $revoked): void { $this->revoked = $revoked; } - public function getRevoked() + public function getRevoked(): bool { return $this->revoked; } diff --git a/Classes/Domain/Model/AuthCode.php b/Classes/Domain/Model/AuthCode.php index 0fe04ce..1b3ae5c 100644 --- a/Classes/Domain/Model/AuthCode.php +++ b/Classes/Domain/Model/AuthCode.php @@ -17,80 +17,69 @@ class AuthCode extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity { - /** @var string */ - protected $identifier; - - /** @var \DateTime */ - protected $expiresAt; - - /** @var string */ - protected $user; - - /** @var string */ - protected $scopes; - - /** @var string */ - protected $client; - - /** @var bool */ - protected $revoked; - - public function setIdentifier($identifier) + protected string $identifier = ''; + protected ?\DateTimeImmutable $expiresAt = null; + protected string $user = ''; + protected string $scopes = ''; + protected string $client = ''; + protected bool $revoked = false; + + public function setIdentifier(string $identifier): void { $this->identifier = $identifier; } - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } - public function setExpiresAt($expiresAt) + public function setExpiresAt(\DateTimeImmutable $expiresAt): void { $this->expiresAt = $expiresAt; } - public function getExpiresAt() + public function getExpiresAt(): ?\DateTimeImmutable { return $this->expiresAt; } - public function setUser($user) + public function setUser(string|int|null $user): void { - $this->user = $user; + $this->user = (string)$user; } - public function getUser() + public function getUser(): string { return $this->user; } - public function setScopes($scopes) + public function setScopes(string $scopes): void { $this->scopes = $scopes; } - public function getScopes() + public function getScopes(): string { return $this->scopes; } - public function setClient($client) + public function setClient(string $client): void { $this->client = $client; } - public function getClient() + public function getClient(): string { return $this->client; } - public function setRevoked($revoked) + public function setRevoked(bool $revoked): void { $this->revoked = $revoked; } - public function getRevoked() + public function getRevoked(): bool { return $this->revoked; } diff --git a/Classes/Domain/Model/Client.php b/Classes/Domain/Model/Client.php index 25d9a3c..c56f6c2 100644 --- a/Classes/Domain/Model/Client.php +++ b/Classes/Domain/Model/Client.php @@ -23,150 +23,80 @@ class Client extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity implements C { use EntityTrait; - /** - * @var string - */ - protected $name; - - /** - * @var string - */ - protected $redirectUri; - - /** - * @var bool - */ - protected $isConfidential = false; - - /** - * @var string - */ - protected $grantType = ''; - - /** - * @var string - */ - protected $secret; - - /** - * @var bool - */ - protected $skipConsent = false; - - /** - * @var string - */ - protected $allowedScopes = ''; - - /** - * Get the value of grantType - * - * @return string - */ + protected string $name = ''; + protected string $redirectUri = ''; + protected bool $isConfidential = false; + protected string $grantType = ''; + protected string $secret = ''; + protected bool $skipConsent = false; + protected string $allowedScopes = ''; + public function getGrantType(): ?string { return $this->grantType; } - /** - * Set the value of grantType - * - * @param string $grantType - */ public function setGrantType(string $grantType): void { $this->grantType = $grantType; } - /** - * Get the value of secret - * - * @return string|null - */ public function getSecret(): ?string { return $this->secret; } - /** - * Set the value of secret - * - * @param string $secret - */ - public function setSecret(string $secret) + public function setSecret(string $secret): void { $this->secret = $secret; } - /** - * Get the value of skipConsent - * - * @return bool - */ public function doSkipConsent(): bool { return $this->skipConsent; } - /** - * Set the value of skipConsent - * - * @param bool $skipConsent - */ - public function setSkipConsent(bool $skipConsent) + public function setSkipConsent(bool $skipConsent): void { $this->skipConsent = $skipConsent; } - /** - * Get the value of allowedScopes - * - * @return string - */ - public function getAllowedScopes() + public function getAllowedScopes(): string { return $this->allowedScopes; } - /** - * Set the value of allowedScopes - * - * @param string $allowedScopes - */ - public function setAllowedScopes(string $allowedScopes) + public function setAllowedScopes(string $allowedScopes): void { $this->allowedScopes = $allowedScopes; } - /** - * Get the client's name. - * - * @return string - * @codeCoverageIgnore - */ - public function getName() + public function getName(): string { return $this->name; } - /** - * Returns the registered redirect URI (as a string). - * - * Alternatively return an indexed array of redirect URIs. - * - * @return string[] - */ - public function getRedirectUri() + public function setName(string $name): void + { + $this->name = $name; + } + + public function setRedirectUri(string $redirectUri): void + { + $this->redirectUri = $redirectUri; + } + + public function getRedirectUri(): array + { + return GeneralUtility::trimExplode("\n", $this->redirectUri); + } + + public function setConfidential(bool $isConfidential): void { - return GeneralUtility::trimExplode('\\n', $this->redirectUri); + $this->isConfidential = $isConfidential; } - /** - * Returns true if the client is confidential. - * - * @return bool - */ - public function isConfidential() + public function isConfidential(): bool { return $this->isConfidential; } diff --git a/Classes/Domain/Model/FrontendUser.php b/Classes/Domain/Model/FrontendUser.php index 2fe3856..01f98e4 100644 --- a/Classes/Domain/Model/FrontendUser.php +++ b/Classes/Domain/Model/FrontendUser.php @@ -19,515 +19,237 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; -/** - * A Frontend User - * - * @deprecated since v11, will be removed in v12. Do not use or extend this model. - */ class FrontendUser extends AbstractEntity { - /** - * @var string - */ - protected $username = ''; - - /** - * @var string - */ - protected $password = ''; - - /** - * @var ObjectStorage - */ - protected $usergroup; - - /** - * @var string - */ - protected $name = ''; - - /** - * @var string - */ - protected $firstName = ''; - - /** - * @var string - */ - protected $middleName = ''; - - /** - * @var string - */ - protected $lastName = ''; - - /** - * @var string - */ - protected $address = ''; - - /** - * @var string - */ - protected $telephone = ''; - - /** - * @var string - */ - protected $fax = ''; - - /** - * @var string - */ - protected $email = ''; - - /** - * @var string - */ - protected $title = ''; - - /** - * @var string - */ - protected $zip = ''; - - /** - * @var string - */ - protected $city = ''; - - /** - * @var string - */ - protected $country = ''; - - /** - * @var string - */ - protected $www = ''; - - /** - * @var string - */ - protected $company = ''; - - /** - * @var ObjectStorage - */ - protected $image; - - /** - * @var \DateTime|null - */ - protected $lastlogin; - - /** - * Called again with initialize object, as fetching an entity from the DB does not use the constructor - */ - public function initializeObject() + protected string $username = ''; + protected string $password = ''; + /** @var ObjectStorage */ + protected ObjectStorage $usergroup; + protected string $name = ''; + protected string $firstName = ''; + protected string $middleName = ''; + protected string $lastName = ''; + protected string $address = ''; + protected string $telephone = ''; + protected string $fax = ''; + protected string $email = ''; + protected string $title = ''; + protected string $zip = ''; + protected string $city = ''; + protected string $country = ''; + protected string $www = ''; + protected string $company = ''; + /** @var ObjectStorage */ + protected ObjectStorage $image; + protected ?\DateTime $lastlogin = null; + + public function __construct() + { + $this->initializeObject(); + } + + public function initializeObject(): void { $this->usergroup = new ObjectStorage(); $this->image = new ObjectStorage(); } - - /** - * Sets the username value - * - * @param string $username - */ - public function setUsername($username) + public function setUsername(string $username): void { $this->username = $username; } - /** - * Returns the username value - * - * @return string - */ - public function getUsername() + public function getUsername(): string { return $this->username; } - /** - * Sets the password value - * - * @param string $password - */ - public function setPassword($password) + public function setPassword(string $password): void { $this->password = $password; } - /** - * Returns the password value - * - * @return string - */ - public function getPassword() + public function getPassword(): string { return $this->password; } /** - * Sets the usergroups. Keep in mind that the property is called "usergroup" - * although it can hold several usergroups. - * * @param ObjectStorage $usergroup */ - public function setUsergroup(ObjectStorage $usergroup) + public function setUsergroup(ObjectStorage $usergroup): void { $this->usergroup = $usergroup; } /** - * Adds a usergroup to the frontend user - * - * @param FrontendUserGroup $usergroup + * @return ObjectStorage */ - public function addUsergroup(FrontendUserGroup $usergroup) - { - $this->usergroup->attach($usergroup); - } - - /** - * Removes a usergroup from the frontend user - * - * @param FrontendUserGroup $usergroup - */ - public function removeUsergroup(FrontendUserGroup $usergroup) - { - $this->usergroup->detach($usergroup); - } - - /** - * Returns the usergroups. Keep in mind that the property is called "usergroup" - * although it can hold several usergroups. - * - * @return ObjectStorage An object storage containing the usergroup - */ - public function getUsergroup() + public function getUsergroup(): ObjectStorage { return $this->usergroup; } - /** - * Sets the name value - * - * @param string $name - */ - public function setName($name) + public function setName(string $name): void { $this->name = $name; } - /** - * Returns the name value - * - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } - /** - * Sets the firstName value - * - * @param string $firstName - */ - public function setFirstName($firstName) + public function setFirstName(string $firstName): void { $this->firstName = $firstName; } - /** - * Returns the firstName value - * - * @return string - */ - public function getFirstName() + public function getFirstName(): string { return $this->firstName; } - /** - * Sets the middleName value - * - * @param string $middleName - */ - public function setMiddleName($middleName) + public function setMiddleName(string $middleName): void { $this->middleName = $middleName; } - /** - * Returns the middleName value - * - * @return string - */ - public function getMiddleName() + public function getMiddleName(): string { return $this->middleName; } - /** - * Sets the lastName value - * - * @param string $lastName - */ - public function setLastName($lastName) + public function setLastName(string $lastName): void { $this->lastName = $lastName; } - /** - * Returns the lastName value - * - * @return string - */ - public function getLastName() + public function getLastName(): string { return $this->lastName; } - /** - * Sets the address value - * - * @param string $address - */ - public function setAddress($address) + public function setAddress(string $address): void { $this->address = $address; } - /** - * Returns the address value - * - * @return string - */ - public function getAddress() + public function getAddress(): string { return $this->address; } - /** - * Sets the telephone value - * - * @param string $telephone - */ - public function setTelephone($telephone) + public function setTelephone(string $telephone): void { $this->telephone = $telephone; } - /** - * Returns the telephone value - * - * @return string - */ - public function getTelephone() + public function getTelephone(): string { return $this->telephone; } - /** - * Sets the fax value - * - * @param string $fax - */ - public function setFax($fax) + public function setFax(string $fax): void { $this->fax = $fax; } - /** - * Returns the fax value - * - * @return string - */ - public function getFax() + public function getFax(): string { return $this->fax; } - /** - * Sets the email value - * - * @param string $email - */ - public function setEmail($email) + public function setEmail(string $email): void { $this->email = $email; } - /** - * Returns the email value - * - * @return string - */ - public function getEmail() + public function getEmail(): string { return $this->email; } - /** - * Sets the title value - * - * @param string $title - */ - public function setTitle($title) + public function setTitle(string $title): void { $this->title = $title; } - /** - * Returns the title value - * - * @return string - */ - public function getTitle() + public function getTitle(): string { return $this->title; } - /** - * Sets the zip value - * - * @param string $zip - */ - public function setZip($zip) + public function setZip(string $zip): void { $this->zip = $zip; } - /** - * Returns the zip value - * - * @return string - */ - public function getZip() + public function getZip(): string { return $this->zip; } - /** - * Sets the city value - * - * @param string $city - */ - public function setCity($city) + public function setCity(string $city): void { $this->city = $city; } - /** - * Returns the city value - * - * @return string - */ - public function getCity() + public function getCity(): string { return $this->city; } - /** - * Sets the country value - * - * @param string $country - */ - public function setCountry($country) + public function setCountry(string $country): void { $this->country = $country; } - /** - * Returns the country value - * - * @return string - */ - public function getCountry() + public function getCountry(): string { return $this->country; } - /** - * Sets the www value - * - * @param string $www - */ - public function setWww($www) + public function setWww(string $www): void { $this->www = $www; } - /** - * Returns the www value - * - * @return string - */ - public function getWww() + public function getWww(): string { return $this->www; } - /** - * Sets the company value - * - * @param string $company - */ - public function setCompany($company) + public function setCompany(string $company): void { $this->company = $company; } - /** - * Returns the company value - * - * @return string - */ - public function getCompany() + public function getCompany(): string { return $this->company; } - /** - * Sets the image value - * * @param ObjectStorage $image */ - public function setImage(ObjectStorage $image) + public function setImage(ObjectStorage $image): void { $this->image = $image; } /** - * Gets the image value - * * @return ObjectStorage */ - public function getImage() + public function getImage(): ObjectStorage { return $this->image; } - /** - * Sets the lastlogin value - * - * @param \DateTime $lastlogin - */ - public function setLastlogin(\DateTime $lastlogin) + public function setLastlogin(\DateTime $lastlogin): void { $this->lastlogin = $lastlogin; } - /** - * Returns the lastlogin value - * - * @return \DateTime - */ - public function getLastlogin() + public function getLastlogin(): ?\DateTime { return $this->lastlogin; } diff --git a/Classes/Domain/Model/FrontendUserGroup.php b/Classes/Domain/Model/FrontendUserGroup.php index baa9df5..19a19a2 100644 --- a/Classes/Domain/Model/FrontendUserGroup.php +++ b/Classes/Domain/Model/FrontendUserGroup.php @@ -18,117 +18,55 @@ use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; -/** - * A Frontend User Group - * - * @deprecated since v11, will be removed in v12. Do not use or extend this model. - */ class FrontendUserGroup extends AbstractEntity { - /** - * @var string - */ - protected $title = ''; + protected string $title = ''; + protected string $description = ''; + /** @var ObjectStorage */ + protected ObjectStorage $subgroup; - /** - * @var string - */ - protected $description = ''; - - /** - * @var ObjectStorage - */ - protected $subgroup; + public function __construct() + { + $this->initializeObject(); + } - /** - * Constructs a new Frontend User Group - * - * @param string $title - */ - public function __construct($title = '') + public function initializeObject(): void { - $this->setTitle($title); $this->subgroup = new ObjectStorage(); } - /** - * Sets the title value - * - * @param string $title - */ - public function setTitle($title) + public function setTitle(string $title): void { $this->title = $title; } - /** - * Returns the title value - * - * @return string - */ - public function getTitle() + public function getTitle(): string { return $this->title; } - /** - * Sets the description value - * - * @param string $description - */ - public function setDescription($description) + public function setDescription(string $description): void { $this->description = $description; } - /** - * Returns the description value - * - * @return string - */ - public function getDescription() + public function getDescription(): string { return $this->description; } /** - * Sets the subgroups. Keep in mind that the property is called "subgroup" - * although it can hold several subgroups. - * - * @param ObjectStorage $subgroup An object storage containing the subgroups to add + * @param ObjectStorage $subgroup */ - public function setSubgroup(ObjectStorage $subgroup) + public function setSubgroup(ObjectStorage $subgroup): void { $this->subgroup = $subgroup; } /** - * Adds a subgroup to the frontend user - * - * @param FrontendUserGroup $subgroup - */ - public function addSubgroup(FrontendUserGroup $subgroup) - { - $this->subgroup->attach($subgroup); - } - - /** - * Removes a subgroup from the frontend user group - * - * @param FrontendUserGroup $subgroup - */ - public function removeSubgroup(FrontendUserGroup $subgroup) - { - $this->subgroup->detach($subgroup); - } - - /** - * Returns the subgroups. Keep in mind that the property is called "subgroup" - * although it can hold several subgroups. - * - * @return ObjectStorage An object storage containing the subgroups + * @return ObjectStorage */ - public function getSubgroup() + public function getSubgroup(): ObjectStorage { return $this->subgroup; } diff --git a/Classes/Domain/Model/RefreshToken.php b/Classes/Domain/Model/RefreshToken.php index 5cb2167..d8e54a6 100644 --- a/Classes/Domain/Model/RefreshToken.php +++ b/Classes/Domain/Model/RefreshToken.php @@ -17,54 +17,50 @@ class RefreshToken extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity { - /** @var string */ - protected $identifier; + protected string $identifier = ''; - /** @var \DateTime */ - protected $expiresAt; + protected ?\DateTimeImmutable $expiresAt = null; - /** @var bool */ - protected $revoked; + protected bool $revoked = false; - /** @var string */ - protected $accessToken; + protected string $accessToken = ''; - public function setIdentifier($identifier) + public function setIdentifier(string $identifier): void { $this->identifier = $identifier; } - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } - public function setExpiresAt($expiresAt) + public function setExpiresAt(\DateTimeImmutable $expiresAt): void { $this->expiresAt = $expiresAt; } - public function getExpiresAt() + public function getExpiresAt(): ?\DateTimeImmutable { return $this->expiresAt; } - public function setRevoked($revoked) + public function setRevoked(bool $revoked): void { $this->revoked = $revoked; } - public function getRevoked() + public function getRevoked(): bool { return $this->revoked; } - public function setAccessToken($accessToken) + public function setAccessToken(string $accessToken): void { $this->accessToken = $accessToken; } - public function getAccessToken() + public function getAccessToken(): string { return $this->accessToken; } diff --git a/Classes/Domain/Model/Scope.php b/Classes/Domain/Model/Scope.php index e94afca..60cfbca 100644 --- a/Classes/Domain/Model/Scope.php +++ b/Classes/Domain/Model/Scope.php @@ -22,13 +22,10 @@ class Scope extends \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject implemen { use EntityTrait; - /** @var string */ - protected $description = ''; + protected string $description = ''; + protected bool $consent = true; - /** @var bool */ - protected $consent = true; - - public function __construct($name) + public function __construct(string $name) { $this->setIdentifier($name); } @@ -44,17 +41,17 @@ public function getDescription(): string return $this->description; } - public function setDescription(string $description) + public function setDescription(string $description): void { $this->description = $description; } - public function getConsent() + public function getConsent(): bool { return $this->consent; } - public function setConsent(bool $consent) + public function setConsent(bool $consent): void { $this->consent = $consent; } diff --git a/Classes/Domain/Repository/AccessTokenRepository.php b/Classes/Domain/Repository/AccessTokenRepository.php index e561376..7249765 100644 --- a/Classes/Domain/Repository/AccessTokenRepository.php +++ b/Classes/Domain/Repository/AccessTokenRepository.php @@ -18,9 +18,13 @@ * ***/ +/** + * @extends \TYPO3\CMS\Extbase\Persistence\Repository<\R3H6\Oauth2Server\Domain\Model\AccessToken> + * @method ?\R3H6\Oauth2Server\Domain\Model\AccessToken findOneBy(array $criteria) + */ class AccessTokenRepository extends \TYPO3\CMS\Extbase\Persistence\Repository { - public function persist() + public function persist(): void { $this->persistenceManager->persistAll(); } @@ -30,7 +34,7 @@ public function persist() * @param string|int $clientId * @param string[] $scopes */ - public function hasValidAccessToken($userId, $clientId, array $scopes) + public function hasValidAccessToken($userId, $clientId, array $scopes): bool { $query = $this->createQuery(); $query->matching( diff --git a/Classes/Domain/Repository/AuthCodeRepository.php b/Classes/Domain/Repository/AuthCodeRepository.php index a378190..59e4d1e 100644 --- a/Classes/Domain/Repository/AuthCodeRepository.php +++ b/Classes/Domain/Repository/AuthCodeRepository.php @@ -15,9 +15,13 @@ * ***/ +/** + * @extends \TYPO3\CMS\Extbase\Persistence\Repository<\R3H6\Oauth2Server\Domain\Model\AuthCode> + * @method ?\R3H6\Oauth2Server\Domain\Model\AuthCode findOneBy(array $criteria) + */ class AuthCodeRepository extends \TYPO3\CMS\Extbase\Persistence\Repository { - public function persist() + public function persist(): void { $this->persistenceManager->persistAll(); } diff --git a/Classes/Domain/Repository/ClientRepository.php b/Classes/Domain/Repository/ClientRepository.php index 42b66ec..7023149 100644 --- a/Classes/Domain/Repository/ClientRepository.php +++ b/Classes/Domain/Repository/ClientRepository.php @@ -22,11 +22,15 @@ * ***/ +/** + * @extends \TYPO3\CMS\Extbase\Persistence\Repository<\R3H6\Oauth2Server\Domain\Model\Client> + * @method ?\R3H6\Oauth2Server\Domain\Model\Client findOneBy(array $criteria) + */ class ClientRepository extends \TYPO3\CMS\Extbase\Persistence\Repository implements ClientRepositoryInterface, LoggerAwareInterface { use LoggerAwareTrait; - public function initializeObject() + public function initializeObject(): void { /** \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $querySettings */ $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class); @@ -37,24 +41,24 @@ public function initializeObject() public function getClientEntity($clientIdentifier) { $this->logger->debug('Get client', ['identifier' => $clientIdentifier]); - return $this->findOneByIdentifier($clientIdentifier); + return $this->findOneBy(['identifier' => $clientIdentifier]); } public function validateClient($clientIdentifier, $clientSecret, $grantType) { - $client = $this->findOneByIdentifier($clientIdentifier); + $client = $this->findOneBy(['identifier' => $clientIdentifier]); if ($client === null) { $this->logger->debug('No client found', ['identifier' => $clientIdentifier]); return false; } - if (GeneralUtility::inList($client->getGrantType(), $grantType) === false) { + if (GeneralUtility::inList((string)$client->getGrantType(), (string)$grantType) === false) { $this->logger->debug('Grant type not allowed by client', ['identifier' => $clientIdentifier, 'grantType' => $grantType]); return false; } $passwordHashFactory = GeneralUtility::makeInstance(PasswordHashFactory::class); $hashInstance = $passwordHashFactory->getDefaultHashInstance('FE'); - return $hashInstance->checkPassword($clientSecret, $client->getSecret()); + return $hashInstance->checkPassword((string)$clientSecret, (string)$client->getSecret()); } } diff --git a/Classes/Domain/Repository/RefreshTokenRepository.php b/Classes/Domain/Repository/RefreshTokenRepository.php index a577cfe..a093bcd 100644 --- a/Classes/Domain/Repository/RefreshTokenRepository.php +++ b/Classes/Domain/Repository/RefreshTokenRepository.php @@ -4,6 +4,9 @@ namespace R3H6\Oauth2Server\Domain\Repository; +use R3H6\Oauth2Server\Domain\Model\RefreshToken; +use TYPO3\CMS\Extbase\Persistence\Repository; + /*** * * This file is part of the "OAuth2 Server" Extension for TYPO3 CMS. @@ -15,9 +18,13 @@ * ***/ -class RefreshTokenRepository extends \TYPO3\CMS\Extbase\Persistence\Repository +/** + * @extends Repository + * @method ?\R3H6\Oauth2Server\Domain\Model\RefreshToken findOneBy(array $criteria) + */ +class RefreshTokenRepository extends Repository { - public function persist() + public function persist(): void { $this->persistenceManager->persistAll(); } diff --git a/Classes/Domain/Repository/ScopeRepository.php b/Classes/Domain/Repository/ScopeRepository.php index 4b28ea9..739d15b 100644 --- a/Classes/Domain/Repository/ScopeRepository.php +++ b/Classes/Domain/Repository/ScopeRepository.php @@ -13,6 +13,7 @@ use R3H6\Oauth2Server\Domain\Model\Client; use R3H6\Oauth2Server\Domain\Model\Scope; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\Repository; /*** * @@ -25,14 +26,16 @@ * ***/ -class ScopeRepository extends \TYPO3\CMS\Extbase\Persistence\Repository implements ScopeRepositoryInterface, LoggerAwareInterface +/** + * @extends Repository + */ +class ScopeRepository extends Repository implements ScopeRepositoryInterface, LoggerAwareInterface { use LoggerAwareTrait; - /** - * @var Configuration - */ - protected $configuration; + public function __construct( + protected Configuration $configuration + ) {} public function getScopeEntityByIdentifier($identifier) { @@ -61,9 +64,4 @@ public function finalizeScopes(array $scopes, $grantType, ClientEntityInterface } return $scopes; } - - public function injectConfiguration(Configuration $configuration): void - { - $this->configuration = $configuration; - } } diff --git a/Classes/Domain/Repository/UserRepository.php b/Classes/Domain/Repository/UserRepository.php index a72c570..30fed61 100644 --- a/Classes/Domain/Repository/UserRepository.php +++ b/Classes/Domain/Repository/UserRepository.php @@ -8,9 +8,11 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; +use R3H6\Oauth2Server\Domain\Model\User; use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; +use TYPO3\CMS\Extbase\Persistence\Repository; /*** * @@ -23,15 +25,18 @@ * ***/ -class UserRepository extends \TYPO3\CMS\Extbase\Persistence\Repository implements UserRepositoryInterface, LoggerAwareInterface +/** + * @extends Repository + * @method ?\R3H6\Oauth2Server\Domain\Model\User findOneBy(array $criteria) + */ +class UserRepository extends Repository implements UserRepositoryInterface, LoggerAwareInterface { use LoggerAwareTrait; - public function initializeObject() + public function initializeObject(): void { /** \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $querySettings */ $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class); - $querySettings->setRespectStoragePage(false); $this->setDefaultQuerySettings($querySettings); } @@ -39,7 +44,7 @@ public function initializeObject() public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity) { $this->logger->debug('Get user', ['username' => $username]); - $user = $this->findOneByUsername($username); + $user = $this->findOneBy(['username' => $username]); if ($user === null) { $this->logger->debug('No user found', ['username' => $username]); throw new \RuntimeException('Username or password invalid', 1607636289929); diff --git a/Classes/Middleware/Dispatcher.php b/Classes/Middleware/Dispatcher.php index 1f8aa42..484a35c 100644 --- a/Classes/Middleware/Dispatcher.php +++ b/Classes/Middleware/Dispatcher.php @@ -2,11 +2,13 @@ namespace R3H6\Oauth2Server\Middleware; +use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use R3H6\Oauth2Server\ExceptionHandlingTrait; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Http\DispatcherInterface; @@ -74,14 +76,16 @@ private function checkConstraints(ServerRequestInterface $request, array $expres $variables['request'] = $request; $language = new ExpressionLanguage(); - foreach ($defaultProvider->getExpressionLanguageProviders() as $provider) { - $language->registerProvider(GeneralUtility::makeInstance($provider)); + foreach ($defaultProvider->getExpressionLanguageProviders() as $providerClass) { + $provider = GeneralUtility::makeInstance($providerClass); + assert($provider instanceof ExpressionFunctionProviderInterface); + $language->registerProvider($provider); } foreach ($expressions as $expression) { $result = $language->evaluate($expression, $variables); if ($result === false) { - throw new \Exception("Constraint failed: $expression"); + throw OAuthServerException::accessDenied("Constraint failed: $expression"); } } } diff --git a/Classes/Middleware/Initializer.php b/Classes/Middleware/Initializer.php index 766e737..1b78a27 100644 --- a/Classes/Middleware/Initializer.php +++ b/Classes/Middleware/Initializer.php @@ -10,6 +10,9 @@ use R3H6\Oauth2Server\Configuration\Configuration; use R3H6\Oauth2Server\ExceptionHandlingTrait; use R3H6\Oauth2Server\Routing\RouterFactory; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode; +use TYPO3\CMS\Core\TypoScript\FrontendTypoScript; use TYPO3\CMS\Core\Utility\GeneralUtility; /*** @@ -30,15 +33,23 @@ class Initializer implements MiddlewareInterface public function __construct( private readonly RouterFactory $routerFactory, private readonly Configuration $configuration, + private readonly ExtensionConfiguration $extensionConfiguration, ) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - $siteConfiguration = $request->getAttribute('site')->getConfiguration()['oauth2'] ?? false; + $siteConfiguration = $request->getAttribute('site')?->getConfiguration()['oauth2'] ?? false; + if ($siteConfiguration === false || !($siteConfiguration['enabled'] ?? true)) { return $handler->handle($request); } + $typoscript = new FrontendTypoScript(new RootNode(), []); + $typoscript->setSetupArray([]); + $request = $request->withAttribute('frontend.typoscript', $typoscript); + $GLOBALS['TYPO3_REQUEST'] = $request; + + $this->configuration->merge($this->extensionConfiguration->get('oauth2_server')); $this->configuration->merge($siteConfiguration); $router = $this->routerFactory->fromRequest($request); diff --git a/Classes/Mvc/Controller/AuthorizationContext.php b/Classes/Mvc/Controller/AuthorizationContext.php index 760e878..bf73957 100644 --- a/Classes/Mvc/Controller/AuthorizationContext.php +++ b/Classes/Mvc/Controller/AuthorizationContext.php @@ -23,11 +23,17 @@ class AuthorizationContext { + private readonly FrontendUserAuthentication $frontendUser; + private readonly Site $site; + public function __construct( private readonly ServerRequestInterface $request, private readonly AuthorizationRequest $authRequest, private readonly Configuration $configuration, - ) {} + ) { + $this->frontendUser = $this->request->getAttribute('frontend.user') ?: throw new \InvalidArgumentException('Frontend user must be authenticated', 1718222682952); + $this->site = $this->request->getAttribute('site') ?: throw new \InvalidArgumentException('Site must be set', 1718222689630); + } public function getRequest(): ServerRequestInterface { @@ -46,12 +52,12 @@ public function getConfiguration(): Configuration public function getFrontendUser(): FrontendUserAuthentication { - return $this->request->getAttribute('frontend.user'); + return $this->frontendUser; } public function getSite(): Site { - return $this->request->getAttribute('site'); + return $this->site; } public function isAuthenticated(): bool @@ -61,6 +67,6 @@ public function isAuthenticated(): bool public function getFrontendUserUid(): ?int { - return $this->getFrontendUser()->user['uid'] ?? null; + return $this->frontendUser->user['uid'] ?? null; } } diff --git a/Classes/Service/Oauth2AuthService.php b/Classes/Service/Oauth2AuthService.php index 713ac48..84a7fa3 100644 --- a/Classes/Service/Oauth2AuthService.php +++ b/Classes/Service/Oauth2AuthService.php @@ -32,7 +32,7 @@ public function getRequest(): ServerRequestInterface return $this->authInfo['request']; } - public function getUser() + public function getUser(): array|bool { $request = $this->getRequest(); $userId = $request->getAttribute('oauth_user_id') ?? null; diff --git a/Classes/Session/Session.php b/Classes/Session/Session.php index 70d96fb..0b042c0 100644 --- a/Classes/Session/Session.php +++ b/Classes/Session/Session.php @@ -4,6 +4,7 @@ use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; /*** @@ -23,16 +24,20 @@ final class Session public static function fromRequest(ServerRequestInterface $request): self { - return new self($request->getAttribute('frontend.user')); + $frontendUser = $request->getAttribute('frontend.user'); + if ($frontendUser instanceof FrontendUserAuthentication === false) { + throw new \RuntimeException('FrontendUserAuthentication not found in request attributes', 1718220842598); + } + return GeneralUtility::makeInstance(self::class, $frontendUser); } - private function __construct( - private readonly FrontendUserAuthentication $frontendUserAuthentication + public function __construct( + private readonly FrontendUserAuthentication $frontendUser ) {} public function get(): ?AuthorizationRequest { - $authRequest = unserialize((string)$this->frontendUserAuthentication->getKey('ses', self::AUTH_REQUEST)); + $authRequest = unserialize((string)$this->frontendUser->getKey('ses', self::AUTH_REQUEST)); if ($authRequest instanceof AuthorizationRequest) { return $authRequest; } @@ -41,11 +46,11 @@ public function get(): ?AuthorizationRequest public function set(AuthorizationRequest $authRequest): void { - $this->frontendUserAuthentication->setKey('ses', self::AUTH_REQUEST, serialize($authRequest)); + $this->frontendUser->setKey('ses', self::AUTH_REQUEST, serialize($authRequest)); } public function clear(): void { - $this->frontendUserAuthentication->setKey('ses', self::AUTH_REQUEST, null); + $this->frontendUser->setKey('ses', self::AUTH_REQUEST, null); } } diff --git a/Classes/Utility/ScopeUtility.php b/Classes/Utility/ScopeUtility.php index 3342e95..ff0200f 100644 --- a/Classes/Utility/ScopeUtility.php +++ b/Classes/Utility/ScopeUtility.php @@ -24,6 +24,9 @@ public static function toString(ScopeEntityInterface ...$scopes): string return implode(', ', static::toStrings(...$scopes)); } + /** + * @return string[] + */ public static function toStrings(ScopeEntityInterface ...$scopes): array { return array_map(function (ScopeEntityInterface $scope) { diff --git a/Configuration/Routing/config.yaml b/Configuration/Routing/config.yaml index 49fbe0b..6e0bd87 100644 --- a/Configuration/Routing/config.yaml +++ b/Configuration/Routing/config.yaml @@ -46,7 +46,7 @@ oauth_revoke: oauth_test: path: /test - methods: [POST] + methods: [GET, POST] schemes: https options: oauth2_constraints: diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 04a215c..2e12406 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -11,17 +11,6 @@ services: resource: '../Classes/Controller/*' public: true - - - - # R3H6\Oauth2Server\Domain\Repository\ClientRepository: - # calls: - # - initializeObject: [] - - # R3H6\Oauth2Server\Domain\Repository\UserRepository: - # calls: - # - initializeObject: [] - R3H6\Oauth2Server\Domain\Bridge\RequestEvent: public: true diff --git a/ext_tables.php b/Configuration/TCA/Overrides/sys_template.php similarity index 100% rename from ext_tables.php rename to Configuration/TCA/Overrides/sys_template.php diff --git a/Configuration/TCA/tx_oauth2server_domain_model_accesstoken.php b/Configuration/TCA/tx_oauth2server_domain_model_accesstoken.php index da67e44..1f3b3e0 100644 --- a/Configuration/TCA/tx_oauth2server_domain_model_accesstoken.php +++ b/Configuration/TCA/tx_oauth2server_domain_model_accesstoken.php @@ -32,10 +32,8 @@ 'exclude' => false, 'label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_accesstoken.expires_at', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', + 'type' => 'datetime', 'size' => 10, - 'eval' => 'datetime', 'default' => time(), ], ], diff --git a/Configuration/TCA/tx_oauth2server_domain_model_authcode.php b/Configuration/TCA/tx_oauth2server_domain_model_authcode.php index 344f1fa..42a7a89 100644 --- a/Configuration/TCA/tx_oauth2server_domain_model_authcode.php +++ b/Configuration/TCA/tx_oauth2server_domain_model_authcode.php @@ -32,10 +32,8 @@ 'exclude' => false, 'label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_authcode.expires_at', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', + 'type' => 'datetime', 'size' => 10, - 'eval' => 'datetime', 'default' => time(), ], ], diff --git a/Configuration/TCA/tx_oauth2server_domain_model_client.php b/Configuration/TCA/tx_oauth2server_domain_model_client.php index bd670d8..4550b78 100644 --- a/Configuration/TCA/tx_oauth2server_domain_model_client.php +++ b/Configuration/TCA/tx_oauth2server_domain_model_client.php @@ -10,7 +10,6 @@ 'editlock' => 'editlock', 'tstamp' => 'tstamp', 'crdate' => 'crdate', - 'cruser_id' => 'cruser_id', 'delete' => 'deleted', 'enablecolumns' => [ 'disabled' => 'hidden', @@ -61,12 +60,6 @@ 'config' => [ 'type' => 'check', 'renderType' => 'checkboxToggle', - 'items' => [ - [ - 0 => '', - 1 => '', - ], - ], ], ], 'hidden' => [ @@ -77,8 +70,7 @@ 'renderType' => 'checkboxToggle', 'items' => [ [ - 0 => '', - 1 => '', + 'label' => '', 'invertStateDisplay' => true, ], ], @@ -88,9 +80,7 @@ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', - 'eval' => 'datetime,int', + 'type' => 'datetime', 'default' => 0, ], 'l10n_mode' => 'exclude', @@ -100,9 +90,7 @@ 'exclude' => true, 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', - 'eval' => 'datetime,int', + 'type' => 'datetime', 'default' => 0, 'range' => [ 'upper' => mktime(0, 0, 0, 1, 1, 2038), @@ -134,7 +122,8 @@ 'config' => [ 'type' => 'input', 'size' => 30, - 'eval' => 'trim,required', + 'eval' => 'trim', + 'required' => true, ], ], 'secret' => [ @@ -161,11 +150,11 @@ 'type' => 'select', 'renderType' => 'selectCheckBox', 'items' => [ - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.authorization_code', 'authorization_code'], - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.client_credentials', 'client_credentials'], - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.implicit', 'implicit'], - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.password', 'password'], - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.refresh_token', 'refresh_token'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.authorization_code', 'value' => 'authorization_code'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.client_credentials', 'value' => 'client_credentials'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.implicit', 'value' => 'implicit'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.password', 'value' => 'password'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.grant_type.refresh_token', 'value' => 'refresh_token'], ], ], ], @@ -202,7 +191,7 @@ // 'itemsProcFunc' => \R3H6\Oauth2Server\Hook\AllowedScopesItemsProcFunc::class . '->addItems', 'size' => 10, 'items' => [ - ['LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.allowed_scopes.items.0', '--div--'], + ['label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_client.allowed_scopes.items.0', 'value' => '--div--'], ], ], ], diff --git a/Configuration/TCA/tx_oauth2server_domain_model_refreshtoken.php b/Configuration/TCA/tx_oauth2server_domain_model_refreshtoken.php index 535e6c1..972d75e 100644 --- a/Configuration/TCA/tx_oauth2server_domain_model_refreshtoken.php +++ b/Configuration/TCA/tx_oauth2server_domain_model_refreshtoken.php @@ -32,10 +32,8 @@ 'exclude' => false, 'label' => 'LLL:EXT:oauth2_server/Resources/Private/Language/locallang_db.xlf:tx_oauth2server_domain_model_refreshtoken.expires_at', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', + 'type' => 'datetime', 'size' => 10, - 'eval' => 'datetime', 'default' => time(), ], ], diff --git a/Tests/Fixtures/Database/base.csv b/Tests/Fixtures/Database/base.csv new file mode 100644 index 0000000..b6dc8b1 --- /dev/null +++ b/Tests/Fixtures/Database/base.csv @@ -0,0 +1,17 @@ +"pages" +,"uid","pid","doktype","title","slug" +,1,0,1,"Login","/" +,2,1,1,"Consent","/consent" +,3,1,254,"Users","/users" +,4,1,254,"Clients","/clients" +"tx_oauth2server_domain_model_client" +,"uid","pid","name","identifier","secret","grant_type","redirect_uri","is_confidential","skip_consent" +,1,4,"Test1","fc17757d-aa0c-481d-96d7-c2504ce7199a","$argon2i$v=19$m=65536,t=16,p=1$NEtuaGV3YlRZd3FYUExKOQ$XT54GLVXwSkiA7K/Wxgk7PjwusW7Ggt8vayEQoJfVYs","authorization_code","https://localhost/redirect",1,0 +,2,4,"Test2","fc17757d-aa0c-481d-96d7-c2504ce7199b","$argon2i$v=19$m=65536,t=16,p=1$NEtuaGV3YlRZd3FYUExKOQ$XT54GLVXwSkiA7K/Wxgk7PjwusW7Ggt8vayEQoJfVYs","authorization_code","https://localhost/redirect",1,1 +"fe_groups" +,"uid","pid","title" +,1,3,"Test1" +,2,3,"Test2" +"fe_users" +,"uid","pid","username","password","usergroup","name","first_name","middle_name","last_name","address","telephone","fax","email","title","zip","city","country","www","company","image","tstamp" +,"1","3","user","$argon2i$v=19$m=65536,t=16,p=1$d2sudnlVLnFLR2FiZ1R2bQ$6dzuZ230KJGsWGPLrxQEHIOQQViuh1EZW+rHnL8LtT4","1,2","","Kasper","","Skårhøj","Anywhere Rd. 123","+12 34 567 890","","kasper@typo3.org","Mr.","12345","Nowhereville","Denmark","https://typo3.org","TYPO3 GmbH","",1612466634 diff --git a/Tests/Fixtures/config/sites/main/config.yaml b/Tests/Fixtures/config/sites/main/config.yaml new file mode 100644 index 0000000..8adf7cc --- /dev/null +++ b/Tests/Fixtures/config/sites/main/config.yaml @@ -0,0 +1,17 @@ +base: 'https://localhost/' +errorHandling: { } +languages: + - + title: English + enabled: true + languageId: 0 + base: / + locale: en_US.UTF-8 + navigationTitle: English + flag: us +rootPageId: 1 +routes: { } +oauth2: + enabled: true + scopes: + email: 'E-mail' diff --git a/Tests/Functional/AuthorizationServerTest.php b/Tests/Functional/AuthorizationServerTest.php new file mode 100644 index 0000000..b1f115b --- /dev/null +++ b/Tests/Functional/AuthorizationServerTest.php @@ -0,0 +1,104 @@ + 'typo3conf/sites', + ]; + + protected array $configurationToUseInTestInstance = [ + 'EXTENSIONS' => [ + 'oauth2_server' => [ + 'loginPageUid' => 1, + 'consentPageUid' => 2, + ], + ], + 'MAIL' => [ + 'transport' => NullTransport::class, + ], + 'LOG' => [ + 'R3H6' => [ + 'Oauth2Server' => [ + 'writerConfiguration' => [ + \TYPO3\CMS\Core\Log\LogLevel::DEBUG => [ + \TYPO3\CMS\Core\Log\Writer\FileWriter::class => [], + ], + ], + ], + ], + ], + ]; + + protected function setUp(): void + { + parent::setUp(); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/Database/base.csv'); + $this->setUpFrontendRootPage(1, [ + 'constants' => ['EXT:oauth2_server/Configuration/TypoScript/constants.typoscript'], + 'setup' => ['EXT:oauth2_server/Configuration/TypoScript/setup.typoscript'], + ]); + } + + /** + * @test + */ + public function authorizationEndpointRedirectsToLoginPage(): void + { + $request = new InternalRequest('https://localhost/oauth2/authorize?' . http_build_query([ + 'response_type' => 'code', + 'client_id' => 'fc17757d-aa0c-481d-96d7-c2504ce7199a', + 'redirect_uri' => 'https://localhost/redirect', + 'state' => 'bwqjmz2j2gs', + ])); + $context = new InternalRequestContext(); + $response = $this->executeFrontendSubRequest($request, $context); + self::assertStringContainsString('https://localhost/?redirect_url=', $response->getHeaderLine('Location'), 'Response: ' . $response->getBody()); + } + + /** + * @test + */ + public function authorizationEndpointRedirectsToConsentPage(): void + { + $request = new InternalRequest('https://localhost/oauth2/authorize?' . http_build_query([ + 'response_type' => 'code', + 'client_id' => 'fc17757d-aa0c-481d-96d7-c2504ce7199a', + 'redirect_uri' => 'https://localhost/redirect', + 'state' => 'bwqjmz2j2gs', + 'scope' => 'email', + ])); + $context = (new InternalRequestContext())->withFrontendUserId(1); + $response = $this->executeFrontendSubRequest($request, $context); + self::assertStringContainsString('https://localhost/consent', $response->getHeaderLine('Location'), 'Response: ' . $response->getBody()); + } + + /** + * @test + */ + public function authorizationEndpointReturnsAuthCode(): void + { + $request = new InternalRequest('https://localhost/oauth2/authorize?' . http_build_query([ + 'response_type' => 'code', + 'client_id' => 'fc17757d-aa0c-481d-96d7-c2504ce7199b', + 'redirect_uri' => 'https://localhost/redirect', + 'state' => 'bwqjmz2j2gs', + 'scope' => 'email', + ])); + $context = (new InternalRequestContext())->withFrontendUserId(1); + $response = $this->executeFrontendSubRequest($request, $context); + self::assertStringContainsString('https://localhost/redirect?code=', $response->getHeaderLine('Location'), 'Response: ' . $response->getBody()); + } +} diff --git a/Tests/Unit/Domain/Repository/ClientRepositoryTest.php b/Tests/Unit/Domain/Repository/ClientRepositoryTest.php new file mode 100644 index 0000000..f480f19 --- /dev/null +++ b/Tests/Unit/Domain/Repository/ClientRepositoryTest.php @@ -0,0 +1,52 @@ +logger = $this->createMock(LoggerInterface::class); + $this->passwordHashFactory = $this->createMock(PasswordHashFactory::class); + $this->passwordHash = $this->createMock(PasswordHashInterface::class); + + $this->clientRepository = $this->getMockBuilder(ClientRepository::class) + ->setConstructorArgs([$this->logger]) + ->onlyMethods(['findOneBy']) + ->getMock(); + + GeneralUtility::addInstance(PasswordHashFactory::class, $this->passwordHashFactory); + } + + public function testValidateClient(): void + { + $client = $this->createMock(Client::class); + $client->method('getGrantType')->willReturn('password'); + $client->method('getSecret')->willReturn('hashed_password'); + + $this->clientRepository->method('findOneBy')->willReturn($client); + + $this->passwordHashFactory->method('getDefaultHashInstance')->willReturn($this->passwordHash); + $this->passwordHash->method('checkPassword')->willReturn(true); + + $result = $this->clientRepository->validateClient('client_id', 'client_secret', 'password'); + + self::assertTrue($result); + } + + // Add more test methods for each scenario you want to test +} diff --git a/composer.json b/composer.json index 6eb576b..d06d631 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ "require-dev": { "ergebnis/composer-normalize": "^2.42", "michielroos/typo3scan": "^1.7", - "phpmd/phpmd": "^2.15", - "phpspec/prophecy": "^1.19", + "phpstan/phpstan-phpunit": "^1.4", + "phpunit/phpcov": "^9.0", "saschaegerer/phpstan-typo3": "^1.10", "ssch/typo3-rector": "^2.6", "typo3/cms-belog": "^12.4", @@ -76,10 +76,29 @@ "ci:php:lint": "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l", "ci:php:stan": "phpstan --no-progress", "ci:test:unit": "phpunit -c .Build/vendor/typo3/testing-framework/Resources/Core/Build/UnitTests.xml Tests/Unit/", + "ci:test:functional": "phpunit -c .Build/vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTests.xml Tests/Functional/", "ci:typo3:scan": "typo3scan scan Classes/ -i strong", "fix:composer:normalize": "@composer normalize --no-check-lock", "fix:php:cs": "php-cs-fixer fix", - "fix:rector": "rector process", - "test:rector": "rector process --dry-run" + "rector:fix": "rector process", + "rector:test": "rector process --dry-run", + "coverage:create-directories": "mkdir -p .Build/coverage", + "ci:coverage:functional": [ + "@coverage:create-directories", + "XDEBUG_MODE=coverage phpunit -c .Build/vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTests.xml --coverage-filter Classes --coverage-php=.Build/coverage/functional.cov Tests/Functional" + ], + "ci:coverage:merge": [ + "@coverage:create-directories", + "XDEBUG_MODE=coverage phpcov merge --html=.Build/coverage/ .Build/coverage/" + ], + "ci:coverage:unit": [ + "@coverage:create-directories", + "XDEBUG_MODE=coverage phpunit -c .Build/vendor/typo3/testing-framework/Resources/Core/Build/UnitTests.xml --coverage-filter Classes --coverage-php=.Build/coverage/unit.cov Tests/Unit" + ], + "ci:coverage": [ + "@ci:coverage:unit", + "@ci:coverage:functional", + "@ci:coverage:merge" + ] } } diff --git a/ext_emconf.php b/ext_emconf.php index b639451..f34af12 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -17,12 +17,10 @@ 'author' => 'R3 H6', 'author_email' => 'r3h6@outlook.com', 'state' => 'beta', - 'createDirs' => '', - 'clearCacheOnLoad' => 0, 'version' => '2.0.0', 'constraints' => [ 'depends' => [ - 'typo3' => '12.4.0-13.4.99', + 'typo3' => '12.4.0-13.1.99', ], 'conflicts' => [], 'suggests' => [ diff --git a/phpmd-rulset.xml b/phpmd-rulset.xml deleted file mode 100644 index 3ad1913..0000000 --- a/phpmd-rulset.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - TYPO3 extension rulset. - - - - - - - - - - - - - - - diff --git a/phpstan.neon b/phpstan.neon index e7432cc..444754e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,19 +1,18 @@ includes: - - .Build/vendor/saschaegerer/phpstan-typo3/extension.neon + - .Build/vendor/saschaegerer/phpstan-typo3/extension.neon parameters: - parallel: - maximumNumberOfProcesses: 5 - level: 5 - paths: - - Classes/ - excludePaths: - analyse: - - Classes/Security/ - typo3: - requestGetAttributeMapping: - oauth_scopes: array|null - oauth_user_id: int|null - oauth_access_token_id: string|null - oauth2.route: \Symfony\Component\Routing\Route|null - oauth2_constraints: array|string|null + level: 8 + ignoreErrors: + - identifier: missingType.iterableValue + - '#Cannot call method [a-z]+\(\) on Psr\\Log\\LoggerInterface\|null#' + paths: + - Classes/ + - Tests/ + typo3: + requestGetAttributeMapping: + oauth_scopes: array|null + oauth_user_id: int|null + oauth_access_token_id: string|null + oauth2.route: \Symfony\Component\Routing\Route|null + oauth2_constraints: array|string|null diff --git a/rector.php b/rector.php index 0373a68..88ca3fc 100644 --- a/rector.php +++ b/rector.php @@ -19,7 +19,7 @@ __DIR__ . '/Tests', __DIR__ . '/ext_emconf.php', __DIR__ . '/ext_localconf.php', - __DIR__ . '/ext_tables.php', + // __DIR__ . '/ext_tables.php', ]) // uncomment to reach your current PHP version // ->withPhpSets() @@ -48,7 +48,7 @@ __DIR__ . '/**/Configuration/ExtensionBuilder/*', NameImportingPostRector::class => [ 'ext_localconf.php', // This line can be removed since TYPO3 11.4, see https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/11.4/Important-94280-MoveContentsOfExtPhpIntoLocalScopes.html - 'ext_tables.php', // This line can be removed since TYPO3 11.4, see https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/11.4/Important-94280-MoveContentsOfExtPhpIntoLocalScopes.html + // 'ext_tables.php', // This line can be removed since TYPO3 11.4, see https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/11.4/Important-94280-MoveContentsOfExtPhpIntoLocalScopes.html 'ClassAliasMap.php', ], ])