Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from castoredc/allow-all-clients-CI
Browse files Browse the repository at this point in the history
Modify the forked repository for Castor Identity purposes
  • Loading branch information
angelomelonas authored Jan 30, 2024
2 parents 39b33e8 + f82431f commit 91296d7
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 192 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
Forked from [fschmtt/keycloak-rest-api-client-php](https://github.com/fschmtt/keycloak-rest-api-client-php).
This repository has been modified to allow getting credentials for any client, not just the admin client.

[![codecov](https://codecov.io/gh/fschmtt/keycloak-rest-api-client-php/graph/badge.svg?token=uDlQdqBF5V)](https://codecov.io/gh/fschmtt/keycloak-rest-api-client-php)
![PHP Analysis](https://github.com/fschmtt/keycloak-rest-api-client-php/actions/workflows/php-analysis.yml/badge.svg?branch=main)
![PHP Unit](https://github.com/fschmtt/keycloak-rest-api-client-php/actions/workflows/php-unit.yml/badge.svg?branch=main)
Expand All @@ -12,7 +15,7 @@ Inspired by [keycloak/keycloak-nodejs-admin-client](https://github.com/keycloak/
## Installation
Install via Composer:
```bash
composer require fschmtt/keycloak-rest-api-client-php
composer require castoredc/keycloak-rest-api-client-php
```

## Usage
Expand All @@ -22,7 +25,8 @@ Example:
$keycloak = new \Fschmtt\Keycloak\Keycloak(
baseUrl: 'http://keycloak:8080',
username: 'admin',
password: 'admin'
password: 'admin',
realm: 'master' # optional, defaults to 'master'
);

$serverInfo = $keycloak->serverInfo()->get();
Expand Down
116 changes: 58 additions & 58 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
{
"name": "fschmtt/keycloak-rest-api-client-php",
"description": "PHP client to interact with Keycloak's Admin REST API.",
"type": "library",
"require": {
"php": "^8.1",
"ext-json": "*",
"guzzlehttp/guzzle": "^7.3",
"lcobucci/jwt": "^4.1"
},
"require-dev": {
"phpmetrics/phpmetrics": "^2.7",
"phpstan/phpstan": "^1.10",
"symplify/easy-coding-standard": "^11.1",
"ramsey/uuid": "^4.7",
"phpunit/phpunit": "^10"
},
"autoload": {
"psr-4": {
"Fschmtt\\Keycloak\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Fschmtt\\Keycloak\\Test\\": "tests"
}
},
"license": "mit",
"authors": [
{
"name": "Frederik Schmitt",
"email": "frederik.schmitt96@gmail.com"
}
],
"scripts": {
"analyze": [
"@phpstan",
"@psalm"
],
"ecs": "vendor/bin/ecs check",
"ecs:fix": "vendor/bin/ecs check --fix",
"phpstan": "vendor/bin/phpstan analyze",
"psalm": "vendor/bin/psalm",
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "vendor/bin/phpunit --testsuite unit",
"test:integration": "vendor/bin/phpunit --testsuite integration"
},
"scripts-descriptions": {
"analyze": "Run phpstan and psalm analysis",
"ecs": "Run ECS",
"ecs:fix": "Fix ECS errors",
"phpstan": "Run phpstan",
"psalm": "Run psalm",
"test": "Run unit and integration tests",
"test:unit": "Run unit tests",
"test:integration": "Run integration tests (requires a fresh and running Keycloak instance)"
"name": "fschmtt/keycloak-rest-api-client-php",
"description": "PHP client to interact with Keycloak's Admin REST API.",
"type": "library",
"require": {
"php": "^8.1",
"ext-json": "*",
"guzzlehttp/guzzle": "^7.3",
"lcobucci/jwt": "^4.1"
},
"require-dev": {
"phpmetrics/phpmetrics": "^2.7",
"phpstan/phpstan": "^1.10",
"symplify/easy-coding-standard": "^11.1",
"ramsey/uuid": "^4.7",
"phpunit/phpunit": "^10"
},
"autoload": {
"psr-4": {
"Fschmtt\\Keycloak\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Fschmtt\\Keycloak\\Test\\": "tests"
}
},
"license": "mit",
"repositories": [
{
"type": "composer",
"url": "https://satis.castoredc.net/"
}
],
"scripts": {
"analyze": [
"@phpstan",
"@psalm"
],
"ecs": "vendor/bin/ecs check",
"ecs:fix": "vendor/bin/ecs check --fix",
"phpstan": "vendor/bin/phpstan analyze",
"psalm": "vendor/bin/psalm",
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "vendor/bin/phpunit --testsuite unit",
"test:integration": "vendor/bin/phpunit --testsuite integration"
},
"scripts-descriptions": {
"analyze": "Run phpstan and psalm analysis",
"ecs": "Run ECS",
"ecs:fix": "Fix ECS errors",
"phpstan": "Run phpstan",
"psalm": "Run psalm",
"test": "Run unit and integration tests",
"test:unit": "Run unit tests",
"test:integration": "Run integration tests (requires a fresh and running Keycloak instance)"
}
}
107 changes: 0 additions & 107 deletions src/Http/Client.php

This file was deleted.

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

namespace Fschmtt\Keycloak\Http\Client;

use Psr\Http\Message\ResponseInterface;

interface Client
{
/**
* @param array<string, mixed> $options
*/
public function request(string $method, string $path = '', array $options = []): ResponseInterface;

public function isAuthorized(): bool;

}
58 changes: 58 additions & 0 deletions src/Http/Client/KeycloakClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Fschmtt\Keycloak\Http\Client;

use DateTime;
use Fschmtt\Keycloak\Keycloak;
use Fschmtt\Keycloak\OAuth\TokenStorageInterface;
use GuzzleHttp\ClientInterface;

use Psr\Http\Message\ResponseInterface;

abstract class KeycloakClient implements Client
{
public function __construct(
protected readonly Keycloak $keycloak,
protected readonly ClientInterface $httpClient,
protected readonly TokenStorageInterface $tokenStorage,
)
{
}

/**
* @param array<string, mixed> $options
*/
public function request(string $method, string $path = '', array $options = []): ResponseInterface
{
if (!$this->isAuthorized()) {
$this->authorize();
}

$defaultOptions = [
'base_uri' => $this->keycloak->getBaseUrl(),
'headers' => [
'Authorization' => 'Bearer ' . $this->tokenStorage->retrieveAccessToken()->toString(),
],
];

$options = array_merge_recursive($options, $defaultOptions);

return $this->httpClient->request(
$method,
$this->keycloak->getBaseUrl() . $path,
$options
);
}

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

protected abstract function authorize(): void;

/**
* @return array{access_token: string}
*/
protected abstract function fetchTokens(): array;
}
56 changes: 56 additions & 0 deletions src/Http/Client/RealmClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Fschmtt\Keycloak\Http\Client;

use Fschmtt\Keycloak\Keycloak;
use Fschmtt\Keycloak\OAuth\TokenStorageInterface;
use GuzzleHttp\ClientInterface;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Token;

class RealmClient extends KeycloakClient
{
public function __construct(
Keycloak $keycloak,
ClientInterface $httpClient,
TokenStorageInterface $tokenStorage,
private readonly string $realm,
)
{
parent::__construct($keycloak, $httpClient, $tokenStorage);
}

/**
* @return array{access_token: string}
*/
protected function fetchTokens(): array
{
$response = $this->httpClient->request(
'POST',
$this->keycloak->getBaseUrl() . sprintf('/realms/%s/protocol/openid-connect/token', $this->realm),
[
'form_params' => [
'client_id' => $this->keycloak->getClientId(),
'client_secret' => $this->keycloak->getClientSecret(),
'grant_type' => 'client_credentials',
],
]
);

$tokens = json_decode(
$response->getBody()->getContents(),
true,
flags: JSON_THROW_ON_ERROR,
);

return ['access_token' => $tokens['access_token']];
}

protected function authorize(): void
{
$tokens = $this->fetchTokens();
$parser = (new Token\Parser(new JoseEncoder()));

$this->tokenStorage->storeAccessToken($parser->parse($tokens['access_token']));
}
}
Loading

0 comments on commit 91296d7

Please sign in to comment.