Skip to content

Commit

Permalink
Merge pull request #54 from MohammadWaleed/develop
Browse files Browse the repository at this point in the history
prepare v0.23.0
  • Loading branch information
MohammadWaleed authored Jul 2, 2021
2 parents 4d02c10 + 205724b commit d09b890
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 37 deletions.
125 changes: 100 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [Introduction](#introduction)
- [How to use](#how-to-use)
- [Customization](#customization)
- [Supported APIs](#supported-apis)
- [Attack Detection](#attack-detection)
- [Authentication Management](#authentication-management)
Expand Down Expand Up @@ -33,14 +34,10 @@
This is a php client to connect to keycloak admin rest apis with no headache.

Features:

1- Easy to use

2- No need to get token or generate it it's already handled by the client

3- No need to specify any urls other than the base uri

4- No encode/decode for json just data as you expect
1. Easy to use
2. No need to get token or generate it it's already handled by the client
3. No need to specify any urls other than the base uri
4. No encode/decode for json just data as you expect

works with Keycloak 7.0 admin rest api

Expand All @@ -49,19 +46,20 @@ https://www.keycloak.org/docs-api/7.0/rest-api/index.html

# How to use

1- Create new client
#### 1. Create new client

```php
$client = Keycloak\Admin\KeycloakClient::factory([
'realm'=>'master',
'username'=>'admin',
'password'=>'1234',
'client_id'=>'admin-cli',
'baseUri'=>'http://127.0.0.1:8180'
]);
'realm' => 'master',
'username' => 'admin',
'password' => '1234',
'client_id' => 'admin-cli',
'baseUri' => 'http://127.0.0.1:8180',
]);
```

2- Use it

#### 2. Use it

```php
$client->getUsers();
Expand Down Expand Up @@ -94,19 +92,96 @@ $client->getUsers();
*/

$client->createUser([
'username'=>'test',
'email'=>'[email protected]',
'enabled'=>true,
'credentials'=>[
'username' => 'test',
'email' => '[email protected]',
'enabled' => true,
'credentials' => [
[
'type'=>'password',
'value'=>'1234'
]
]
]);
'value'=>'1234',
],
],
]);
```

# Customization

### Supported credentials

It is possible to change the credential's type used to authenticate by changing the configuration of the keycloak client.

Currently, the following credentials are supported
- password credentials, used by default
- to authenticate with a user account
````php
$client = Keycloak\Admin\KeycloakClient::factory([
...
'grant_type' => 'password',
'username' => 'admin',
'password' => '1234',
]);
````
- client credentials
- to authenticate with a client service account
````php
$client = Keycloak\Admin\KeycloakClient::factory([
...
'grant_type' => 'client_credentials',
'client_id' => 'admin-cli',
'client_secret' => '84ab3d98-a0c3-44c7-b532-306f222ce1ff',
]);
````

### Injecting middleware

It is possible to inject [Guzzle client middleware](https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html#middleware)
in the keycloak client configuration using the `middlewares` keyword.

For example:
```php
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;

$client = Keycloak\Admin\KeycloakClient::factory([
...
'middlewares' => [
// throws exceptions when request fails
Middleware::httpErrors(),
// other custom middlewares
Middleware::mapRequest(function (RequestInterface $request) {
return $request;
}),
],
]);
```

### Changing how the token is saved and stored

By default, the token is saved at runtime. This means that the previous token is not used when creating a new client.

You can change customize how the token is stored in the client configuration by implementing your own `TokenStorage`,
an interface which describes how the token is stored and retrieved.
```php
class CustomTokenStorage implements TokenStorage
{
public function getToken()
{
// TODO
}

public function saveToken(array $token)
{
// TODO
}
}

$client = Keycloak\Admin\KeycloakClient::factory([
...
'token_storage' => new CustomTokenStorage(),
]);
```


# Supported APIs

## [Attack Detection](https://www.keycloak.org/docs-api/7.0/rest-api/index.html#_attack_detection_resource)
Expand Down Expand Up @@ -472,7 +547,7 @@ Note: Ids are sent as clientScopeId or clientId and mapperId everything else is
| Get consents granted by the user | ||
| Revoke consent and offline tokens for particular client from user | ||
| Disable all credentials for a user of a specific type | ||
| Send a update account email to the user An email contains a link the user can click to perform a set of required actions. | | |
| Send a update account email to the user An email contains a link the user can click to perform a set of required actions. | executeActionsEmail | ✔️ |
| Get social logins associated with the user | ||
| Add a social login provider to the user | ||
| Remove a social login provider from user | ||
Expand Down
6 changes: 5 additions & 1 deletion src/Admin/KeycloakClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use Keycloak\Admin\Classes\FullBodyLocation;
use Keycloak\Admin\TokenStorages\RuntimeTokenStorage;

/**
* Class KeycloakClient
Expand Down Expand Up @@ -265,6 +266,7 @@
* @method array updateUser(array $args = array()) { @command Keycloak updateUser }
* @method array updatePartialUser(array $args = array()) { @command Keycloak updatePartialUser }
* @method array deleteUser(array $args = array()) { @command Keycloak deleteUser }
* @method array executeActionsEmail(array $args = array()) { @command Keycloak executeActionsEmail }
* @method array addUserToGroup(array $args = array()) { @command Keycloak addUserToGroup }
* @method array deleteUserFromGroup(array $args = array()) { @command Keycloak deleteUserFromGroup }
* @method array resetUserPassword(array $args = array()) { @command Keycloak resetUserPassword }
Expand All @@ -290,6 +292,7 @@ public static function factory($config = array())
'version' => '1.0',
'baseUri' => null,
'verify' => true,
'token_storage' => new RuntimeTokenStorage(),
);

// Create client configuration
Expand All @@ -299,7 +302,6 @@ public static function factory($config = array())

$stack = new HandlerStack();
$stack->setHandler(new CurlHandler());
$stack->push(new RefreshToken());

$middlewares = isset($config["middlewares"]) && is_array($config["middlewares"]) ? $config["middlewares"] : [];
foreach ($middlewares as $middleware) {
Expand All @@ -308,6 +310,8 @@ public static function factory($config = array())
}
}

$stack->push(new RefreshToken($config['token_storage']));

$config['handler'] = $stack;

$description = new Description(include __DIR__ . "/Resources/{$file}");
Expand Down
36 changes: 25 additions & 11 deletions src/Admin/Middleware/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,39 @@
namespace Keycloak\Admin\Middleware;

use GuzzleHttp\Client;
use Keycloak\Admin\TokenStorages\TokenStorage;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class RefreshToken
{
private $token = null;
/**
* @var TokenStorage
*/
private $tokenStorage;

public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}

public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {

$cred = $this->refreshTokenIfNeeded($this->token, $options);
$this->token = $cred;
$token = $this->tokenStorage->getToken();
$cred = $this->refreshTokenIfNeeded($token, $options);
$this->tokenStorage->saveToken($cred);
$request = $request->withHeader('Authorization', 'Bearer ' . $cred['access_token']);
return $handler($request, $options);
return $handler($request, $options)->then(function (ResponseInterface $response) {
if ($response->getStatusCode() >= 400) {
$this->tokenStorage->saveToken([]);
}

return $response;
}, function ($reason) {
$this->tokenStorage->saveToken([]);
throw $reason;
});
};
}

Expand All @@ -39,12 +58,7 @@ protected function refreshTokenIfNeeded($credentials, $options)
return $credentials;
}

$credentials = $this->getAccessToken($credentials, true, $options);

if (empty($credentials['access_token'])) {
$this->credentials = null;
}
return $credentials;
return $this->getAccessToken($credentials, true, $options);
}

/**
Expand Down
43 changes: 43 additions & 0 deletions src/Admin/Resources/keycloak-1_0.php
Original file line number Diff line number Diff line change
Expand Up @@ -5214,6 +5214,49 @@
),
),

'executeActionsEmail' => array(
'uri' => 'auth/admin/realms/{realm}/users/{id}/execute-actions-email',
'description' => 'Send a update account email to the user An email contains a link the user can click to perform a set of required actions.',
'httpMethod' => 'PUT',
'parameters' => array(
'realm' => array(
'location' => 'uri',
'description' => 'realm name (not id!)',
'type' => 'string',
'required' => true,
),
'id' => array(
'location' => 'uri',
'description' => 'User id',
'type' => 'string',
'required' => true,
),
'client_id' => array(
'location' => 'query',
'description' => 'Client id',
'type' => 'string',
'required' => false,
),
'lifespan' => array(
'location' => 'query',
'description' => 'Number of seconds after which the generated token expires',
'type' => 'integer',
'required' => false,
),
'redirect_uri' => array(
'location' => 'query',
'description' => 'Redirect uri',
'type' => 'string',
'required' => false,
),
'actions' => array(
'location' => 'fullBody',
'type' => 'array',
'required' => true
),
),
),

'addUserToGroup' => array(
'uri' => 'auth/admin/realms/{realm}/users/{id}/groups/{groupId}',
'description' => 'Assign a specific user to a specific group',
Expand Down
35 changes: 35 additions & 0 deletions src/Admin/TokenStorages/ChainedTokenStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Keycloak\Admin\TokenStorages;

class ChainedTokenStorage implements TokenStorage
{
/**
* @var array|TokenStorage[]
*/
private $storages;

public function __construct(TokenStorage ...$storages)
{
$this->storages = $storages;
}

public function getToken()
{
foreach ($this->storages as $storage) {
$token = $storage->getToken();
if ($token) {
return $token;
}
}

return null;
}

public function saveToken(array $token)
{
foreach ($this->storages as $storage) {
$storage->saveToken($token);
}
}
}
21 changes: 21 additions & 0 deletions src/Admin/TokenStorages/RuntimeTokenStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Keycloak\Admin\TokenStorages;

class RuntimeTokenStorage implements TokenStorage
{
/**
* @var ?array
*/
private $token = null;

public function getToken()
{
return $this->token;
}

public function saveToken(array $token)
{
$this->token = $token;
}
}
16 changes: 16 additions & 0 deletions src/Admin/TokenStorages/TokenStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Keycloak\Admin\TokenStorages;

interface TokenStorage
{
/**
* @return array|null
*/
public function getToken();

/**
* @param array $token
*/
public function saveToken(array $token);
}

0 comments on commit d09b890

Please sign in to comment.