Skip to content

Commit

Permalink
Add possibility to call custom endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
fschmtt committed Oct 3, 2023
1 parent 242fc4d commit 901e262
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 9 deletions.
36 changes: 36 additions & 0 deletions examples/custom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?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,
]
));
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);
}
}
6 changes: 6 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\Roles;
Expand Down Expand Up @@ -102,6 +103,11 @@ public function roles(): Roles
return new Roles($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);
}
}
123 changes: 123 additions & 0 deletions tests/Unit/Resource/CustomTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace Fschmtt\Keycloak\Test\Unit\Resource;

use Fschmtt\Keycloak\Http\Client;
use Fschmtt\Keycloak\Http\CommandExecutor;
use Fschmtt\Keycloak\Http\Criteria;
use Fschmtt\Keycloak\Http\CustomCommand;
use Fschmtt\Keycloak\Http\CustomQuery;
use Fschmtt\Keycloak\Http\Method;
use Fschmtt\Keycloak\Http\QueryExecutor;
use Fschmtt\Keycloak\Resource\Custom;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(Custom::class)]
class CustomTest extends TestCase
{
public function testRunsCustomQuery(): void
{
$queryExecutor = $this->createMock(QueryExecutor::class);
$queryExecutor->expects(static::once())
->method('executeQuery')
->with(new CustomQuery(
'/admin/my/custom-endpoint',
new Criteria([
'foo' => 'bar',
]),
))
->willReturn([
'return' => 'value',
]);

$custom = new Custom(
$this->createMock(CommandExecutor::class),
$queryExecutor,
$this->createMock(Client::class),
);

$result = $custom->query(new CustomQuery(
'/admin/my/custom-endpoint',
new Criteria([
'foo' => 'bar',
]),
));

static::assertSame([
'return' => 'value',
], $result);
}

public function testRunsCustomCommand(): void
{
$commandExecutor = $this->createMock(CommandExecutor::class);
$commandExecutor->expects(static::once())
->method('executeCommand')
->with(new CustomCommand(
'/admin/my/custom-endpoint',
Method::POST,
[
'foo' => 'bar',
],
));

$custom = new Custom(
$commandExecutor,
$this->createMock(QueryExecutor::class),
$this->createMock(Client::class),
);

$custom->command(new CustomCommand(
'/admin/my/custom-endpoint',
Method::POST,
[
'foo' => 'bar',
],
));
}

public function testRunsCustomRequest(): void
{
$client = $this->createMock(Client::class);
$client->expects(static::once())
->method('request')
->with(
'POST',
'/admin/my/custom-endpoint',
[
'headers' => [
'Custom-Header' => 'custom-value',
],
],
)
->willReturn(new Response(
200,
[],
json_encode([
'return' => 'value',
]),
));

$custom = new Custom(
$this->createMock(CommandExecutor::class),
$this->createMock(QueryExecutor::class),
$client,
);

$response = $custom->request(
'POST',
'/admin/my/custom-endpoint',
[
'headers' => [
'Custom-Header' => 'custom-value',
],
]
);

static::assertSame(200, $response->getStatusCode());
}
}

0 comments on commit 901e262

Please sign in to comment.