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

Add possibility to call custom endpoints #71

Closed
wants to merge 3 commits into from
Closed
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
49 changes: 49 additions & 0 deletions examples/custom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

use Fschmtt\Keycloak\Http\Criteria;
use Fschmtt\Keycloak\Http\CustomCommand;
use Fschmtt\Keycloak\Http\CustomQuery;
use Fschmtt\Keycloak\Http\Method;
use Fschmtt\Keycloak\Keycloak;

require_once __DIR__ . '/../vendor/autoload.php';

$keycloak = new Keycloak(
baseUrl: $_SERVER['KEYCLOAK_BASE_URL'] ?? 'http://keycloak:8080',
username: 'admin',
password: 'admin',
);

$custom = $keycloak->custom();

$response = $custom->query(new CustomQuery(
'/admin/custom/endpoint',
new Criteria([
'key' => 'value',
'bool' => true,
])
));

$custom->command(new CustomCommand(
'/admin/custom/endpoint',
Method::POST,
[
'key' => 'value',
'bool' => true,
]
));

$custom->request(
Method::GET->value,
'/admin/custom/endpoint',
[
'headers' => [
'Custom-Header' => 'Custom-Value',
],
'json' => [
'custom' => 'body',
],
],
);
6 changes: 4 additions & 2 deletions src/Http/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Fschmtt\Keycloak\Http;

use DateTime;
use Fschmtt\Keycloak\Keycloak;
use Fschmtt\Keycloak\OAuth\TokenStorageInterface;
use GuzzleHttp\ClientInterface;
Expand Down Expand Up @@ -35,6 +34,7 @@ public function request(string $method, string $path = '', array $options = []):
'base_uri' => $this->keycloak->getBaseUrl(),
'headers' => [
'Authorization' => 'Bearer ' . $this->tokenStorage->retrieveAccessToken()->toString(),
'Content-Type' => 'application/json',
],
];

Expand All @@ -49,7 +49,9 @@ public function request(string $method, string $path = '', array $options = []):

public function isAuthorized(): bool
{
return $this->tokenStorage->retrieveAccessToken() !== null && !$this->tokenStorage->retrieveAccessToken()->isExpired(new DateTime());
$accessToken = $this->tokenStorage->retrieveAccessToken();

return $accessToken && !$accessToken->isExpired(new \DateTimeImmutable());
}

private function authorize(): void
Expand Down
10 changes: 7 additions & 3 deletions src/Http/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ class Command
public function __construct(
private readonly string $path,
private readonly Method $method,
/** @var array<string, string> */
/** @var array<string, string> $parameters */
private readonly array $parameters = [],
private readonly Representation|Collection|null $payload = null,
/** @var Representation|Collection|array<mixed>|null $payload */
private readonly Representation|Collection|array|null $payload = null,
) {
}

Expand All @@ -39,7 +40,10 @@ public function getPath(): string
);
}

public function getPayload(): Representation|Collection|null
/**
* @return Representation|Collection|array<mixed>|null
*/
public function getPayload(): Representation|Collection|array|null
{
return $this->payload;
}
Expand Down
14 changes: 10 additions & 4 deletions src/Http/CommandExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,25 @@ public function executeCommand(Command $command): void

protected function prepareBody(Command $command): ?string
{
if ($command->getPayload() === null) {
$payload = $command->getPayload();

if ($payload === null) {
return null;
}

$jsonEncoder = new JsonEncoder();

if ($command->getPayload() instanceof Representation) {
return $jsonEncoder->encode($this->propertyFilter->filter($command->getPayload()));
if (is_array($payload)) {
return $jsonEncoder->encode($payload);
}

if ($payload instanceof Representation) {
return $jsonEncoder->encode($this->propertyFilter->filter($payload));
}

$representations = [];

foreach ($command->getPayload() as $representation) {
foreach ($payload as $representation) {
$representations[] = $this->propertyFilter->filter($representation);
}

Expand Down
22 changes: 22 additions & 0 deletions src/Http/CustomCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Http;

use Fschmtt\Keycloak\Collection\Collection;
use Fschmtt\Keycloak\Representation\Representation;

class CustomCommand extends Command
{
/**
* @param Representation|Collection|array<mixed>|null $payload
*/
public function __construct(
string $path,
Method $method,
Representation|Collection|array|null $payload = null,
) {
parent::__construct($path, $method, [], $payload);
}
}
16 changes: 16 additions & 0 deletions src/Http/CustomQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Http;

class CustomQuery extends Query
{
public function __construct(
string $path,
?Criteria $criteria = null,
string $returnType = 'array',
) {
parent::__construct($path, $returnType, [], $criteria);
}
}
9 changes: 9 additions & 0 deletions src/Keycloak.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Fschmtt\Keycloak\OAuth\TokenStorageInterface;
use Fschmtt\Keycloak\Resource\AttackDetection;
use Fschmtt\Keycloak\Resource\Clients;
use Fschmtt\Keycloak\Resource\Custom;
use Fschmtt\Keycloak\Resource\Groups;
use Fschmtt\Keycloak\Resource\Realms;
use Fschmtt\Keycloak\Resource\Resource;
Expand All @@ -21,6 +22,9 @@
use Fschmtt\Keycloak\Serializer\Factory as SerializerFactory;
use GuzzleHttp\Client as GuzzleClient;

/**
* @codeCoverageIgnore
*/
class Keycloak
{
private ?string $version = null;
Expand Down Expand Up @@ -121,6 +125,11 @@ public function resource(string $resource): Resource
return new $resource($this->commandExecutor, $this->queryExecutor);
}

public function custom(): Custom
{
return new Custom($this->commandExecutor, $this->queryExecutor, $this->client);
}

private function fetchVersion(): void
{
if ($this->version) {
Expand Down
41 changes: 41 additions & 0 deletions src/Resource/Custom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Resource;

use Fschmtt\Keycloak\Http\Client;
use Fschmtt\Keycloak\Http\CommandExecutor;
use Fschmtt\Keycloak\Http\CustomCommand;
use Fschmtt\Keycloak\Http\CustomQuery;
use Fschmtt\Keycloak\Http\QueryExecutor;
use Psr\Http\Message\ResponseInterface;

class Custom extends Resource
{
public function __construct(
CommandExecutor $commandExecutor,
QueryExecutor $queryExecutor,
private readonly Client $client,
) {
parent::__construct($commandExecutor, $queryExecutor);
}

public function query(CustomQuery $query): mixed
{
return $this->queryExecutor->executeQuery($query);
}

public function command(CustomCommand $command): void
{
$this->commandExecutor->executeCommand($command);
}

/**
* @param array<string, mixed> $options
*/
public function request(string $method, string $path, array $options = []): ResponseInterface
{
return $this->client->request($method, $path, $options);
}
}
5 changes: 3 additions & 2 deletions src/Resource/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

use Fschmtt\Keycloak\Http\CommandExecutor;
use Fschmtt\Keycloak\Http\QueryExecutor;
use PHPUnit\Framework\Attributes\IgnoreClassForCodeCoverage;

#[IgnoreClassForCodeCoverage(self::class)]
/**
* @codeCoverageIgnore
*/
abstract class Resource
{
public function __construct(
Expand Down
31 changes: 31 additions & 0 deletions tests/Unit/Http/CommandExecutorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,35 @@ public function testCallsClientWithBodyIfCommandHasCollection(): void
$executor = new CommandExecutor($client, new PropertyFilter());
$executor->executeCommand($command);
}

public function testCallsClientWithBodyIfCommandHasArrayPayload(): void
{
$payload = [
'custom' => 'payload',
];

$command = new Command(
'/path/to/resource',
Method::PUT,
[],
$payload,
);

$client = $this->createMock(Client::class);
$client->expects(static::once())
->method('request')
->with(
Method::PUT->value,
'/path/to/resource',
[
'body' => (new JsonEncoder())->encode($payload),
'headers' => [
'Content-Type' => 'application/json',
],
]
);

$executor = new CommandExecutor($client, new PropertyFilter());
$executor->executeCommand($command);
}
}
12 changes: 12 additions & 0 deletions tests/Unit/Http/CommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ public function testCanGetCollectionPayload(): void
);
}

public function testCanGetArrayPayload(): void
{
$payload = [
'custom' => 'payload',
];

static::assertSame(
$payload,
(new Command('/path', Method::POST, [], $payload))->getPayload()
);
}

public function testSubstitutesParametersInPath(): void
{
static::assertSame(
Expand Down
42 changes: 42 additions & 0 deletions tests/Unit/Http/CustomCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Test\Unit\Http;

use Fschmtt\Keycloak\Http\CustomCommand;
use Fschmtt\Keycloak\Http\Method;
use Fschmtt\Keycloak\Test\Unit\Stub\Representation;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(CustomCommand::class)]
class CustomCommandTest extends TestCase
{
public function testCustomCommandWithoutPayload(): void
{
$customCommand = new CustomCommand(
'/path/to/resource',
Method::PUT,
);

static::assertSame('/path/to/resource', $customCommand->getPath());
static::assertSame(Method::PUT, $customCommand->getMethod());
static::assertNull($customCommand->getPayload());
}

public function testCustomCommandWithRepresentationPayload(): void
{
$representation = new Representation();

$customCommand = new CustomCommand(
'/path/to/resource',
Method::PUT,
$representation,
);

static::assertSame('/path/to/resource', $customCommand->getPath());
static::assertSame(Method::PUT, $customCommand->getMethod());
static::assertSame($representation, $customCommand->getPayload());
}
}
38 changes: 38 additions & 0 deletions tests/Unit/Http/CustomQueryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Test\Unit\Http;

use Fschmtt\Keycloak\Http\CustomQuery;
use Fschmtt\Keycloak\Http\Method;
use Fschmtt\Keycloak\Representation\User;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(CustomQuery::class)]
class CustomQueryTest extends TestCase
{
public function testCustomCommandWithoutReturnType(): void
{
$customQuery = new CustomQuery(
'/path/to/resource',
);

static::assertSame('/path/to/resource', $customQuery->getPath());
static::assertEquals(Method::GET, $customQuery->getMethod());
static::assertSame('array', $customQuery->getReturnType());
}

public function testCustomCommandWithRepresentationReturnType(): void
{
$customQuery = new CustomQuery(
'/path/to/resource',
returnType: User::class,
);

static::assertSame('/path/to/resource', $customQuery->getPath());
static::assertEquals(Method::GET, $customQuery->getMethod());
static::assertSame(User::class, $customQuery->getReturnType());
}
}
Loading
Loading