Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix UpdateClientCommand options #192

Open
wants to merge 1 commit into
base: v3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
106 changes: 75 additions & 31 deletions Command/UpdateClientCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,30 @@ protected function configure(): void
->addOption(
'redirect-uri',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.',
[]
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs. Use it without value to remove existing values.',
[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@v-m-i I have several issues with this approach:

  1. this hole [0] approach seems kind of hacky, why not just leave [] as the default & use empty:
    image
  2. what if someone runs it like this: bin/console trikoder:oauth2:update-client 1 --redirect-uri=url --redirect-uri, they would get:
    image

I'm thinking maybe this would be a better approach:

  1. leave InputOption::VALUE_REQUIRED & [] as the default
  2. add --remove-* equivalents, eg --remove-grant-type

Then if someone has a client with the scopes s1 & s2, they could do: trikoder:oauth2:update-client 1 --scope=s3 --remove-scope=s1 & would end up with a client that has the scopes s2 & s3. Hint: you could use array_merge & array_diff.

@spideyfusion @X-Coder264 Any thoughts on this one?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @HypeMC

  1. I agree it is hacky, you are right, there is no reason to use [0] when I could have used [], I will change it if we chose this "set" approach.

  2. This comand sets attributes it has recieved, meaning that after running trikoder:oauth2:update-client 1 --scope=s3 client 1 would have only s3 scope, regardless of how many scopes it had before.

We have two requirements to satisfy here:
1. User can remove all scopes from client
2. User can set scopes for client

I think that in order to satisfy first requirement our InputOption must be VALUE_OPTIONAL because trikoder:oauth2:update-client 1 --scope is the only way to remove all scopes with "set" approach?

Add/Remove approach you have mentioned is something I would like more than this "set" approach. If we would decide to go with that approach, I would suggest that --scope, and other methods are prefixed with add, like this: trikoder:oauth2:update-client 1 --add-scope=s3 --remove-scope=s1

)
->addOption(
'grant-type',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sets allowed grant type for client. Use this option multiple times to set multiple grant types.',
[]
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Sets allowed grant type for client. Use this option multiple times to set multiple grant types. Use it without value to remove existing values.',
[0]
)
->addOption(
'scope',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sets allowed scope for client. Use this option multiple times to set multiple scopes.',
[]
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Sets allowed scope for client. Use this option multiple times to set multiple scopes. Use it without value to remove existing values.',
[0]
)
->addOption(
'deactivated',
'active',
null,
InputOption::VALUE_NONE,
'If provided, it will deactivate the given client.'
InputOption::VALUE_REQUIRED,
'Client active state, 1 for active, 0 for inactive',
null
)
->addArgument(
'identifier',
Expand Down Expand Up @@ -90,26 +91,69 @@ protected function execute(InputInterface $input, OutputInterface $output): int

private function updateClientFromInput(Client $client, InputInterface $input): Client
{
$client->setActive(!$input->getOption('deactivated'));

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

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

$scopes = array_map(
static function (string $scope): Scope { return new Scope($scope); },
$input->getOption('scope')
);
$client->setScopes(...$scopes);
$active = $input->getOption('active');

if (null !== $active) {
$client->setActive((bool) $active);
}

$redirectUrisArray = $this->getNullableOption($input, 'redirect-uri');

if (null !== $redirectUrisArray) {
$redirectUris = array_map(
static function (string $redirectUri): RedirectUri {
return new RedirectUri($redirectUri);
},
$redirectUrisArray
);
$client->setRedirectUris(...$redirectUris);
}

$grantsArray = $this->getNullableOption($input, 'grant-type');

if (null !== $grantsArray) {
$grants = array_map(
static function (string $grant): Grant {
return new Grant($grant);
},
$grantsArray
);
$client->setGrants(...$grants);
}

$scopesArray = $this->getNullableOption($input, 'scope');

if (null !== $scopesArray) {
$scopes = array_map(
static function (string $scope): Scope {
return new Scope($scope);
},
$scopesArray
);
$client->setScopes(...$scopes);
}

return $client;
}

private function getNullableOption(InputInterface $input, string $name): ?array
{
$value = $input->getOption($name);

if (
\array_key_exists(0, $value)
&& 0 === $value[0] //if user has entered some value it will always be string so it is fine to rely on 0
) {
return null;
}

if (
\array_key_exists(0, $value)
&& null === $value[0] //when option has mode InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY and no value is sent, option will have value [null]
) {
return [];
}

return $value;
}
}
24 changes: 22 additions & 2 deletions Tests/Acceptance/UpdateClientCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,27 @@ public function testUpdateScopes(): void
$this->assertCount(2, $client->getScopes());
}

public function testDeactivate(): void
public function testSetClientActive(): void
{
$client = $this->fakeAClient('foobar');
$client->setActive(false);
$this->getClientManager()->save($client);
$this->assertFalse($client->isActive());

$command = $this->application->find('trikoder:oauth2:update-client');
$commandTester = new CommandTester($command);
$commandTester->execute([
'command' => $command->getName(),
'identifier' => $client->getIdentifier(),
'--active' => 1,
]);
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Given oAuth2 client updated successfully', $output);
$updatedClient = $this->getClientManager()->find($client->getIdentifier());
$this->assertTrue($updatedClient->isActive());
}

public function testSetClientInactive(): void
{
$client = $this->fakeAClient('foobar');
$this->getClientManager()->save($client);
Expand All @@ -75,7 +95,7 @@ public function testDeactivate(): void
$commandTester->execute([
'command' => $command->getName(),
'identifier' => $client->getIdentifier(),
'--deactivated' => true,
'--active' => 0,
]);
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Given oAuth2 client updated successfully', $output);
Expand Down