Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
db82e08
Add support for the "authorization_code" grant type
ajgarlag Jan 16, 2019
b4e41c5
Add some integration tests
ajgarlag Jan 18, 2019
34d6e4e
Add final integration tests
ajgarlag Jan 21, 2019
6aace21
Use an event to resolve authorization request
ajgarlag Jan 21, 2019
4e8a508
Minor fixes
ajgarlag Jan 22, 2019
b1b67e8
Remove unused dependencies
ajgarlag Jan 22, 2019
a79d41e
Use ScopeConverter to convert scopes
ajgarlag Jan 22, 2019
87ab8cc
Improve naming and semantics
ajgarlag Jan 24, 2019
c89cfb8
Adds "OpenID Connect" support
ajgarlag Jan 22, 2019
84a2a5a
Move the authorization check into an EventListener
ajgarlag Feb 7, 2019
c2ca3f5
Remove authorization check
ajgarlag Feb 7, 2019
2e4c9c7
Fix README
ajgarlag Feb 7, 2019
7e4d68a
Merge remote-tracking branch 'ajgarlag/feature/authorization_code'
MichaelKubovic Feb 7, 2019
074a27e
Merge remote-tracking branch 'ajgarlag/feature/openid-connect'
MichaelKubovic Feb 7, 2019
345c0b6
Rename `decisionUri` to `resolutionUri`
ajgarlag Feb 8, 2019
98720dd
Merge remote-tracking branch 'ajgarlag/feature/authorization_code'
MichaelKubovic Feb 8, 2019
a8dd556
Add return types to AuthorizationRequestResolveEvent
ajgarlag Feb 11, 2019
cc4a780
refactored authoriztion controller to independent event listeners
MichaelKubovic Feb 12, 2019
050fc3a
refactored authoriztion controller to independent event listeners
MichaelKubovic Feb 12, 2019
2754317
adds authorization strategy configuration
MichaelKubovic Feb 13, 2019
b2c1108
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Feb 13, 2019
fd2f6f8
configure openid connect authentiction listener
MichaelKubovic Feb 13, 2019
39e479f
Use Security class instead of TokenStorageInterface
ajgarlag Feb 19, 2019
c060226
Use integers instead of booleans values for authorization resolution.
ajgarlag Feb 19, 2019
b6f385a
Test authorization code request with faked redirect uri
ajgarlag Feb 19, 2019
d45129d
Rename AuthCode to AuthorizationCode
ajgarlag Feb 20, 2019
f3ada2f
Method AbstractIntegrationTest::handleAuthorizeRequest returns Respon…
ajgarlag Feb 20, 2019
6f9e8b0
Rename test methods
ajgarlag Mar 8, 2019
57fe8d2
Update the AuthorizationRequestResolveEvent workflow
ajgarlag Mar 8, 2019
ddb8720
Merge remote-tracking branch 'ajgarlag/feature/authorization_code' in…
MichaelKubovic Apr 9, 2019
f109684
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Apr 10, 2019
fbafd72
configure authentication listener properly
MichaelKubovic Apr 10, 2019
882eeb3
use PSR response interface, reorder methods
MichaelKubovic Apr 10, 2019
7b5cf26
configure consent strategy correctly
MichaelKubovic Apr 10, 2019
475ffb2
configure authentication listener properly
MichaelKubovic Apr 10, 2019
afc1b50
use PSR response interface, reorder methods
MichaelKubovic Apr 10, 2019
6c27e70
configure consent strategy correctly
MichaelKubovic Apr 10, 2019
4527719
reordered methods
MichaelKubovic Apr 10, 2019
0f12aa3
Merge branch 'feature/authorization_code' into develop
MichaelKubovic Apr 10, 2019
f385bd1
get definition by id instead of alias
MichaelKubovic May 9, 2019
5348443
Made authentication listener extendable, fixed service configurations
MichaelKubovic May 10, 2019
6e4e68f
Merge branch 'master' into develop
MichaelKubovic May 20, 2019
f48d907
make properties protected to make them accesible for subclasses
MichaelKubovic May 21, 2019
79cc5cf
adds nonce support
MichaelKubovic May 27, 2019
de9f8de
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic May 27, 2019
6075e5e
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic Jun 21, 2019
4d84913
brings nonce back after merge
MichaelKubovic Jun 21, 2019
dffde0c
approveAuthorization method name changed in upstream
MichaelKubovic Jun 21, 2019
c8712d4
Merge pull request #97 from trikoder/v2.x
spideyfusion Aug 13, 2019
2158c1d
Merge remote-tracking branch 'upstream/v2.x' into develop
MichaelKubovic Sep 7, 2019
1a44f60
fixes after merge
MichaelKubovic Sep 7, 2019
1f8524b
Merge remote-tracking branch 'upstream/master' into develop
MichaelKubovic Sep 30, 2019
8d8fe9f
Reorganized listeners to follow the upstream structure
MichaelKubovic Oct 9, 2019
8c0573b
fixed the use of listener
Tayfun74 Dec 27, 2019
667ed6e
redirect fix
Tayfun74 Jan 2, 2020
ceaedb9
Merge pull request #2 from Tayfun74/develop
MichaelKubovic Jan 2, 2020
766c513
Upgrade lib to V3
Mar 20, 2020
22ba0a2
Update event-dispatcher-constracts constraints
Mar 24, 2020
c28b7e7
Merge pull request #3 from MichaelKubovic/upgrade-to-v3
MichaelKubovic May 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/Tests export-ignore
/dev export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php_cs.dist export-ignore
/.travis.yml export-ignore
/docker-compose.yml export-ignore
/phpunit.xml.dist export-ignore
1 change: 1 addition & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ return PhpCsFixer\Config::create()
'yoda_style' => true,
'compact_nullable_typehint' => true,
'visibility_required' => true,
'nullable_type_declaration_for_default_null_value' => true,
])
->setRiskyAllowed(true)
->setFinder($finder)
Expand Down
38 changes: 18 additions & 20 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
dist: bionic

sudo: required

language: bash

services:
- docker

addons:
apt:
packages:
- docker-ce

env:
# PHP 7.2
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=3.4.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=4.2.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=4.3.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=3.4.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=4.2.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=4.3.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=5.0.*
- PHP_VERSION=7.2 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=5.0.*

# PHP 7.3
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=3.4.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=4.2.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=nyholm SYMFONY_VERSION=4.3.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=3.4.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=4.2.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=zendframework SYMFONY_VERSION=4.3.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=5.0.*
- PHP_VERSION=7.3 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=5.0.*

# PHP 7.4
- PHP_VERSION=7.4 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.4 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=4.4.*
- PHP_VERSION=7.4 PSR_HTTP_PROVIDER=nyholm SYMFONY_REQUIRE=5.0.*
- PHP_VERSION=7.4 PSR_HTTP_PROVIDER=zendframework SYMFONY_REQUIRE=5.0.*

install:
- dev/bin/docker-compose build --build-arg PHP_VERSION=${PHP_VERSION} php

before_script:
# Our docker image has symfony/flex installed to make sure SYMFONY_VERSION is working
- dev/bin/php composer config extra.symfony.require "${SYMFONY_VERSION}"
# Our docker image has symfony/flex installed to make sure SYMFONY_REQUIRE is working
- dev/bin/php composer update --ansi --prefer-dist

script:
- dev/bin/php composer test -- --colors=always --coverage-clover=coverage.xml --debug
- dev/bin/php-test composer test -- --colors=always --coverage-clover=coverage.xml --debug
- dev/bin/php composer lint -- --ansi --diff --dry-run --using-cache=no --verbose

after_script:
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [3.0.0] - 2020-02-26
### Added
- Ability to restrict clients from using the `plain` challenge method during PKCE ([4562a1f](https://github.com/trikoder/oauth2-bundle/commit/4562a1ff306375fd651aa91c85d0d4fd6f4c1b13))
- Ability to clear expired authorization codes ([91b6447](https://github.com/trikoder/oauth2-bundle/commit/91b6447257419d8e961c4f5b0abd187f1b735856))
- Support for defining public (non-confidential) clients ([8a71f55](https://github.com/trikoder/oauth2-bundle/commit/8a71f55aa1482d00cee66684141cc9ef81d31f31))
- The bundle is now compatible with Symfony 5.x ([3f36977](https://github.com/trikoder/oauth2-bundle/commit/3f369771385c0b90855da712b9cb31faa4c651dc))

### Changed
- [PSR-7 Bridge](https://github.com/symfony/psr-http-message-bridge) version constraint to `^2.0` ([3c741ca](https://github.com/trikoder/oauth2-bundle/commit/3c741ca1e394886e8936ad018c28cd1ddd3dff02))
- The bundle now relies on `8.x` versions of [league/oauth2-server](https://github.com/thephpleague/oauth2-server) for base functionality ([8becc18](https://github.com/trikoder/oauth2-bundle/commit/8becc18255052a73d0f76a030be9de0fe9868928))

### Removed
- Support for Symfony 3.4, 4.2 and 4.3 ([3f36977](https://github.com/trikoder/oauth2-bundle/commit/3f369771385c0b90855da712b9cb31faa4c651dc))

## [2.1.1] - 2020-02-25
### Added
- The bundle is now additionally tested against PHP 7.4 ([2b29be3](https://github.com/trikoder/oauth2-bundle/commit/2b29be3629877a648f4a199b96185b40d625f6aa))

### Fixed
- Authentication provider not being aware of the current firewall context ([d349329](https://github.com/trikoder/oauth2-bundle/commit/d349329056c219969e097ae6bd3eb724968f9812))
- Faulty logic when revoking authorization codes ([24ad882](https://github.com/trikoder/oauth2-bundle/commit/24ad88211cefddf97170f5c1cc8ba1e5cf285e42))

## [2.1.0] - 2019-12-09
### Added
- Ability to change the scope role prefix using the `role_prefix` configuration option ([b2ee617](https://github.com/trikoder/oauth2-bundle/commit/b2ee6179832cc142d95e3b13d9af09d6cb6831d5))
- Interfaces for converter type service classes ([d2caf69](https://github.com/trikoder/oauth2-bundle/commit/d2caf690839523a2c84d967a6f99787898d4c654))
- New testing target in Travis CI for Symfony 4.4 ([8a44fd4](https://github.com/trikoder/oauth2-bundle/commit/8a44fd4d7673467cc4f69988424cdfc677767aab))
- The bundle is now fully compatible with [Symfony Flex](https://github.com/symfony/flex) ([a4ccea1](https://github.com/trikoder/oauth2-bundle/commit/a4ccea1dfaaba6d95daf3e1f1a84952cafb65d01))

### Changed
- [DoctrineBundle](https://github.com/doctrine/DoctrineBundle) version constraint to allow `2.x` derived versions ([885e398](https://github.com/trikoder/oauth2-bundle/commit/885e39811331e89bae99bca71f1a783497d26d12))
- Explicitly list [league/oauth2-server](https://github.com/thephpleague/oauth2-server) version requirements in the documentation ([9dce66a](https://github.com/trikoder/oauth2-bundle/commit/9dce66a089c33c224fe5cb58bdfd6285350a607b))
- Reduce distributed package size by excluding files that are used only for development ([80b9e41](https://github.com/trikoder/oauth2-bundle/commit/80b9e41155e7a94c3b1a4602c8daa25cc6d246b2))
- Simplify `AuthorizationRequestResolveEvent` class creation ([32908c1](https://github.com/trikoder/oauth2-bundle/commit/32908c1a4a89fd89d5835d4de931d237de223b50))

### Fixed
- Not being able to delete clients that have access/refresh tokens assigned to them ([424b770](https://github.com/trikoder/oauth2-bundle/commit/424b770dbd99e4651777a3fa26186a756b4e93c4))

## [2.0.1] - 2019-08-13
### Removed
- PSR-7/17 alias check during the container compile process ([0847ea3](https://github.com/trikoder/oauth2-bundle/commit/0847ea3034cc433c9c8f92ec46fedbdace259e3d))
Expand Down
87 changes: 64 additions & 23 deletions Command/ClearExpiredTokensCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Trikoder\Bundle\OAuth2Bundle\Manager\AccessTokenManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\AuthorizationCodeManagerInterface;
use Trikoder\Bundle\OAuth2Bundle\Manager\RefreshTokenManagerInterface;

final class ClearExpiredTokensCommand extends Command
Expand All @@ -26,31 +27,44 @@ final class ClearExpiredTokensCommand extends Command
*/
private $refreshTokenManager;

/**
* @var AuthorizationCodeManagerInterface
*/
private $authorizationCodeManager;

public function __construct(
AccessTokenManagerInterface $accessTokenManager,
RefreshTokenManagerInterface $refreshTokenManager
RefreshTokenManagerInterface $refreshTokenManager,
AuthorizationCodeManagerInterface $authorizationCodeManager
) {
parent::__construct();

$this->accessTokenManager = $accessTokenManager;
$this->refreshTokenManager = $refreshTokenManager;
$this->authorizationCodeManager = $authorizationCodeManager;
}

protected function configure(): void
{
$this
->setDescription('Clears all expired access and/or refresh tokens')
->setDescription('Clears all expired access and/or refresh tokens and/or auth codes')
->addOption(
'access-tokens-only',
'access-tokens',
'a',
InputOption::VALUE_NONE,
'Clear only access tokens.'
'Clear expired access tokens.'
)
->addOption(
'refresh-tokens-only',
'refresh-tokens',
'r',
InputOption::VALUE_NONE,
'Clear only refresh tokens.'
'Clear expired refresh tokens.'
)
->addOption(
'auth-codes',
'c',
InputOption::VALUE_NONE,
'Clear expired auth codes.'
)
;
}
Expand All @@ -59,33 +73,60 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$clearExpiredAccessTokens = !$input->getOption('refresh-tokens-only');
$clearExpiredRefreshTokens = !$input->getOption('access-tokens-only');
$clearExpiredAccessTokens = $input->getOption('access-tokens');
$clearExpiredRefreshTokens = $input->getOption('refresh-tokens');
$clearExpiredAuthCodes = $input->getOption('auth-codes');

if (!$clearExpiredAccessTokens && !$clearExpiredRefreshTokens) {
$io->error('Please choose only one of the following options: "access-tokens-only", "refresh-tokens-only".');
if (!$clearExpiredAccessTokens && !$clearExpiredRefreshTokens && !$clearExpiredAuthCodes) {
$this->clearExpiredAccessTokens($io);
$this->clearExpiredRefreshTokens($io);
$this->clearExpiredAuthCodes($io);

return 1;
return 0;
}

if (true === $clearExpiredAccessTokens) {
$numOfClearedAccessTokens = $this->accessTokenManager->clearExpired();
$io->success(sprintf(
'Cleared %d expired access token%s.',
$numOfClearedAccessTokens,
1 === $numOfClearedAccessTokens ? '' : 's'
));
$this->clearExpiredAccessTokens($io);
}

if (true === $clearExpiredRefreshTokens) {
$numOfClearedRefreshTokens = $this->refreshTokenManager->clearExpired();
$io->success(sprintf(
'Cleared %d expired refresh token%s.',
$numOfClearedRefreshTokens,
1 === $numOfClearedRefreshTokens ? '' : 's'
));
$this->clearExpiredRefreshTokens($io);
}

if (true === $clearExpiredAuthCodes) {
$this->clearExpiredAuthCodes($io);
}

return 0;
}

private function clearExpiredAccessTokens(SymfonyStyle $io): void
{
$numOfClearedAccessTokens = $this->accessTokenManager->clearExpired();
$io->success(sprintf(
'Cleared %d expired access token%s.',
$numOfClearedAccessTokens,
1 === $numOfClearedAccessTokens ? '' : 's'
));
}

private function clearExpiredRefreshTokens(SymfonyStyle $io): void
{
$numOfClearedRefreshTokens = $this->refreshTokenManager->clearExpired();
$io->success(sprintf(
'Cleared %d expired refresh token%s.',
$numOfClearedRefreshTokens,
1 === $numOfClearedRefreshTokens ? '' : 's'
));
}

private function clearExpiredAuthCodes(SymfonyStyle $io): void
{
$numOfClearedAuthCodes = $this->authorizationCodeManager->clearExpired();
$io->success(sprintf(
'Cleared %d expired auth code%s.',
$numOfClearedAuthCodes,
1 === $numOfClearedAuthCodes ? '' : 's'
));
}
}
39 changes: 34 additions & 5 deletions Command/CreateClientCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Trikoder\Bundle\OAuth2Bundle\Command;

use InvalidArgumentException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -67,13 +68,33 @@ protected function configure(): void
InputArgument::OPTIONAL,
'The client secret'
)
->addOption(
'public',
null,
InputOption::VALUE_NONE,
'Create a public client.'
)
->addOption(
'allow-plain-text-pkce',
null,
InputOption::VALUE_NONE,
'Create a client who is allowed to use plain challenge method for PKCE.'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$client = $this->buildClientFromInput($input);

try {
$client = $this->buildClientFromInput($input);
} catch (InvalidArgumentException $exception) {
$io->error($exception->getMessage());

return 1;
}

$this->clientManager->save($client);
$io->success('New oAuth2 client created successfully.');

Expand All @@ -89,25 +110,33 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function buildClientFromInput(InputInterface $input): Client
{
$identifier = $input->getArgument('identifier') ?? hash('md5', random_bytes(16));
$secret = $input->getArgument('secret') ?? hash('sha512', random_bytes(32));

$isPublic = $input->getOption('public');

if (null !== $input->getArgument('secret') && $isPublic) {
throw new InvalidArgumentException('The client cannot have a secret and be public.');
}

$secret = $isPublic ? null : $input->getArgument('secret') ?? hash('sha512', random_bytes(32));

$client = new Client($identifier, $secret);
$client->setActive(true);
$client->setAllowPlainTextPkce($input->getOption('allow-plain-text-pkce'));

$redirectUris = array_map(
function (string $redirectUri): RedirectUri { return new RedirectUri($redirectUri); },
static function (string $redirectUri): RedirectUri { return new RedirectUri($redirectUri); },
$input->getOption('redirect-uri')
);
$client->setRedirectUris(...$redirectUris);

$grants = array_map(
function (string $grant): Grant { return new Grant($grant); },
static function (string $grant): Grant { return new Grant($grant); },
$input->getOption('grant-type')
);
$client->setGrants(...$grants);

$scopes = array_map(
function (string $scope): Scope { return new Scope($scope); },
static function (string $scope): Scope { return new Scope($scope); },
$input->getOption('scope')
);
$client->setScopes(...$scopes);
Expand Down
13 changes: 7 additions & 6 deletions Command/ListClientsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class ListClientsCommand extends Command
public function __construct(ClientManagerInterface $clientManager)
{
parent::__construct();

$this->clientManager = $clientManager;
}

Expand Down Expand Up @@ -82,13 +83,13 @@ private function getFindByCriteria(InputInterface $input): ClientFilter
return
ClientFilter
::create()
->addGrantCriteria(...array_map(function (string $grant): Grant {
->addGrantCriteria(...array_map(static function (string $grant): Grant {
return new Grant($grant);
}, $input->getOption('grant-type')))
->addRedirectUriCriteria(...array_map(function (string $redirectUri): RedirectUri {
->addRedirectUriCriteria(...array_map(static function (string $redirectUri): RedirectUri {
return new RedirectUri($redirectUri);
}, $input->getOption('redirect-uri')))
->addScopeCriteria(...array_map(function (string $scope): Scope {
->addScopeCriteria(...array_map(static function (string $scope): Scope {
return new Scope($scope);
}, $input->getOption('scope')))
;
Expand All @@ -104,7 +105,7 @@ private function drawTable(InputInterface $input, OutputInterface $output, array

private function getRows(array $clients, array $columns): array
{
return array_map(function (Client $client) use ($columns): array {
return array_map(static function (Client $client) use ($columns): array {
$values = [
'identifier' => $client->getIdentifier(),
'secret' => $client->getSecret(),
Expand All @@ -113,7 +114,7 @@ private function getRows(array $clients, array $columns): array
'grant type' => implode(', ', $client->getGrants()),
];

return array_map(function (string $column) use ($values): string {
return array_map(static function (string $column) use ($values): string {
return $values[$column];
}, $columns);
}, $clients);
Expand All @@ -122,7 +123,7 @@ private function getRows(array $clients, array $columns): array
private function getColumns(InputInterface $input): array
{
$requestedColumns = $input->getOption('columns');
$requestedColumns = array_map(function (string $column): string {
$requestedColumns = array_map(static function (string $column): string {
return strtolower(trim($column));
}, $requestedColumns);

Expand Down
Loading