Skip to content

Commit

Permalink
Merge pull request #125 from City-of-Helsinki/UHF-8525
Browse files Browse the repository at this point in the history
UHF-8525: Refactored PubSub service to use Vault
  • Loading branch information
tuutti authored Sep 5, 2023
2 parents 1da31a4 + dd53422 commit 834d8ba
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 67 deletions.
12 changes: 0 additions & 12 deletions config/schema/helfi_api_base.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,6 @@ helfi_api_base.api_accounts:
sequence:
type: string

helfi_api_base.pubsub.settings:
type: config_object
mapping:
endpoint:
type: string
access_key:
type: string
hub:
type: string
group:
type: string

helfi_api_base.environment_resolver.settings:
type: config_entity
mapping:
Expand Down
55 changes: 47 additions & 8 deletions documentation/api-accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,28 @@ This can be used to:

## Managing local API accounts

This is used to ensure that local API accounts retain the credentials. Any missing accounts are created and the password is reset to the one defined in configuration.
This is used to ensure that local API accounts retain the credentials. Any missing accounts are created, and the password is reset to whatever is defined in the configuration.

### Configuration

Define an array of `username`, `password` and an optional `roles` and `mail` pairs:

```php
$config['helfi_api_base.api_accounts']['accounts'][] = [
'username' => 'account1',
'password' => 'password1',
'roles' => ['role1', 'role2'],
'mail' => '[email protected]',
];
```

If no `mail` is provided, an autogenerated email address like `[email protected]` is used. For example: `[email protected]`.

### Using environment variable to define accounts

Define an environment variable called `DRUPAL_API_ACCOUNTS`. These accounts are read and mapped in [settings.php](https://github.com/City-of-Helsinki/drupal-helfi-platform/blob/main/public/sites/default/settings.php) file shipped with `City-of-Helsinki/drupal-helfi-platform`.

The value should be a base64 encoded JSON string that contains an array of `username`, `password` and an optional `roles` and `mail` pairs:
The value should be a base64 encoded JSON string of whatever is defined in `helfi_api_base.api_accounts.accounts` configuration, for example:

```bash
php -r "print base64_encode('[{"username":"account1","password":"password1","roles":["role1","role2"]},{"username":"account2","password":"password2","mail":"[email protected]"}]');"
Expand All @@ -23,8 +40,6 @@ Then map the given output to `DRUPAL_API_ACCOUNTS` environment variable:
DRUPAL_API_ACCOUNTS=W3t1c2VybmFtZTphY2NvdW50MSxwYXNzd29yZDpwYXNzd29yZDEscm9sZXM6W3JvbGUxLHJvbGUyXX0se3VzZXJuYW1lOmFjY291bnQyLHBhc3N3b3JkOnBhc3N3b3JkMixtYWlsOnNvbWUtZW1haWxAZXhhbXBsZS5jb219XQ==
```

If no `mail` is provided, an email address like `[email protected]` is used. For example: `[email protected]`.

### Usage

We hook into `helfi_api_base.post_deploy` event ([src/EventSubscriber/EnsureApiAccountsSubscriber.php](/src/EventSubscriber/EnsureApiAccountsSubscriber.php)), triggered by `drush helfi:post-deploy` command executed as a part of deployment tasks: [https://github.com/City-of-Helsinki/drupal-helfi-platform/blob/main/docker/openshift/entrypoints/20-deploy.sh](https://github.com/City-of-Helsinki/drupal-helfi-platform/blob/main/docker/openshift/entrypoints/20-deploy.sh)
Expand All @@ -50,12 +65,36 @@ $config['helfi_api_base.api_accounts']['accounts'] = $api_accounts;

This is used to store external API credentials.

### Configuration

Define an array of `id`, `plugin`, and `data` pairs:

```php
$config['helfi_api_base.api_accounts']['vault'][] = [
'id' => 'pubsub',
'plugin' => 'json',
'data' => '{"endpoint": "xxx.docker.so", "hub": "local", "group": "invalidate_cache", "access_key": "<access-key>"}',
];
$config['helfi_api_base.api_accounts']['vault'][] = [
'id' => 'global_navigation',
'plugin' => 'authorization_token',
'data' => 'aGVsZmktYWRtaW46MTIz',
];
```

The value of `data` field depends on `plugin` value:

- Authorization token (`authorization_token`): A simple string. For example `aGVsZmktYWRtaW46MTIz`.
- JSON (`json`): A JSON string. For example `{"endpoint": "xxxx.docker.so", "key": "value"}`.

### Using environment variable to define Vault items

Define an environment variable called `DRUPAL_VAULT_ACCOUNTS`. These accounts are read and mapped in [settings.php](https://github.com/City-of-Helsinki/drupal-helfi-platform/blob/main/public/sites/default/settings.php) file shipped with `City-of-Helsinki/drupal-helfi-platform`.

The value should be a base64 encoded JSON string that contains an array of `id`, `plugin` and `data` pairs:
The value should be a base64 encoded JSON string of whatever is defined in `helfi_api_base.api_accounts.vault` configuration, for example:

```bash
php -r "print base64_encode('[{"id": "etusivu_local", "plugin": "authorization_token": "data": "aGVsZmktYWRtaW46MTIz"}]');"
php -r "print base64_encode('[{"id": "global_navigation", "plugin": "authorization_token": "data": "aGVsZmktYWRtaW46MTIz"}]');"
```

Then map the given output to `DRUPAL_VAULT_ACCOUNTS` environment variable:
Expand All @@ -70,8 +109,8 @@ DRUPAL_VAULT_ACCOUNTS=W3tpZDogZXR1c2l2dV9sb2NhbCwgcGx1Z2luOiBhdXRob3JpemF0aW9uX3
/** @var \Drupal\helfi_api_base\Vault\VaultManager $service */
$service = \Drupal::service('helfi_api_base.vault_manager');
/** @var \Drupal\helfi_api_base\Vault\VaultItemInterface $item */
$item = $service->get('etusivu_local'); // 'etusivu_local' is the ID previously defined in DRUPAL_VAULT_ACCOUNTS.
$id = $item->id(); // $id = 'etusivu_local'.
$item = $service->get('global_navigation'); // 'global_navigation' is the ID previously defined in DRUPAL_VAULT_ACCOUNTS.
$id = $item->id(); // $id = 'global_navigation'.
$data = $item->data() // $data = 'aGVsZmktYWRtaW46MTIz'. This is a base64 encoded basic auth token (helfi-admin:123).
```

Expand Down
26 changes: 14 additions & 12 deletions documentation/pubsub-messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ Provides an integration to [Azure's Web PubSub service](https://azure.microsoft.

## Configuration

You must define the following settings to use this feature:
You must define a [JSON Vault item](/documentation/api-accounts.md#managing-external-api-credentials) to use this feature. The data field should be a JSON string containing `endpoint`, `hub`, `group` and `access_key`:

```php
$config['helfi_api_base.pubsub.settings']['access_key'] = '<access-key>';
// Url to Azure's wss endpoint, usually something like: yourservicename.webpubsub.azure.com
$config['helfi_api_base.pubsub.settings']['endpoint'] = '<url to azure pubsub endpoint>';
// Hub and group must be same in all instances that talk with each other.
$config['helfi_api_base.pubsub.settings']['hub'] = '<hub>';
$config['helfi_api_base.pubsub.settings']['group'] = '<group>';
```json
{"endpoint": "<endpoint>", "hub": "<hub>", "group": "<group>", "access_key": "<access-key>"}
```

## Usage
Expand Down Expand Up @@ -82,8 +77,15 @@ See [CacheTagInvalidatorSubscriber](/src/EventSubscriber/CacheTagInvalidatorSubs

```php
# public/sites/default/local.settings.php
$config['helfi_api_base.pubsub.settings']['access_key'] = '<access-key>';
$config['helfi_api_base.pubsub.settings']['endpoint'] = '<url to azure pubsub endpoint>';
$config['helfi_api_base.pubsub.settings']['hub'] = '<hub>';
$config['helfi_api_base.pubsub.settings']['group'] = '<group>';
$pubsub_account = [
'id' => 'pubsub',
'plugin' => 'json',
'data' => json_encode(
'endpoint' => '<endpoint-here>',
'hub' => '<hub>',
'group' => '<group>',
'access_key' => '<access-key>',
]),
];
$config['helfi_api_base.api_accounts']['vault'][] = $pubsub_account;
```
2 changes: 1 addition & 1 deletion helfi_api_base.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ services:
helfi_api_base.pubsub_settings_factory:
class: Drupal\helfi_api_base\Azure\PubSub\SettingsFactory
arguments:
- '@config.factory'
- '@helfi_api_base.vault_manager'

helfi_api_base.pubsub_client_factory:
class: Drupal\helfi_api_base\Azure\PubSub\PubSubClientFactory
Expand Down
34 changes: 24 additions & 10 deletions src/Azure/PubSub/SettingsFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@

namespace Drupal\helfi_api_base\Azure\PubSub;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\helfi_api_base\Vault\VaultManager;

/**
* A factory to initialize Settings object.
* A factory to initialize a Settings object.
*/
final class SettingsFactory {

/**
* Constructs a new instance.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config factory service.
* @param \Drupal\helfi_api_base\Vault\VaultManager $vaultManager
* The vault manager.
*/
public function __construct(
private readonly ConfigFactoryInterface $configFactory
private readonly VaultManager $vaultManager,
) {
}

Expand All @@ -29,13 +29,27 @@ public function __construct(
* The PubSub settings object.
*/
public function create() : Settings {
$config = $this->configFactory->get('helfi_api_base.pubsub.settings');
$data = (object) [
'hub' => '',
'group' => '',
'endpoint' => '',
'access_key' => '',
];

if ($settings = $this->vaultManager->get('pubsub')) {
foreach ($data as $key => $value) {
if (!isset($settings->data()->{$key})) {
continue;
}
$data->{$key} = $settings->data()->{$key};
}
}

return new Settings(
$config->get('hub') ?: '',
$config->get('group') ?: '',
$config->get('endpoint') ?: '',
$config->get('access_key') ?: '',
$data->hub ?: '',
$data->group ?: '',
$data->endpoint ?: '',
$data->access_key ?: ''
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Commands/PubSubCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Usage:
*
* $ drush helfi:azure-pubsub-listen
* This will listen and process messages until the MAX_MESSAGES is
* This will listen to and process messages until the MAX_MESSAGES is
* reached and then exits with code 0.
*/
final class PubSubCommands extends DrushCommands {
Expand Down
58 changes: 58 additions & 0 deletions src/Vault/Json.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types = 1);

namespace Drupal\helfi_api_base\Vault;

/**
* A value object to store string item vault items.
*/
final class Json implements VaultItemInterface {

public const PLUGIN = 'json';

/**
* The json decoded data.
*
* @var object|mixed
*/
private readonly object $data;

/**
* Constructs a new instance.
*
* @param string $id
* The ID.
* @param string $string
* The JSON string.
*
* @throws \JsonException
*/
public function __construct(
private readonly string $id,
string $string,
) {
$this->data = json_decode($string, flags: JSON_THROW_ON_ERROR);
}

/**
* Gets the id.
*
* @return string
* The ID.
*/
public function id() : string {
return $this->id;
}

/**
* Gets the data.
*
* @return object
* The data.
*/
public function data() : object {
return $this->data;
}

}
4 changes: 3 additions & 1 deletion src/Vault/VaultManagerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class VaultManagerFactory {
* The config factory.
*/
public function __construct(
private ConfigFactoryInterface $configFactory,
private readonly ConfigFactoryInterface $configFactory,
) {
}

Expand All @@ -36,8 +36,10 @@ public function create() : VaultManager {
if (!isset($item['plugin'], $item['id'], $item['data'])) {
throw new \InvalidArgumentException('Missing required "plugin", "id" or "data".');
}

return match($item['plugin']) {
AuthorizationToken::PLUGIN => new AuthorizationToken($item['id'], $item['data']),
Json::PLUGIN => new Json($item['id'], $item['data']),
};
}, $config);

Expand Down
18 changes: 13 additions & 5 deletions tests/src/Kernel/Cache/CacheTagInvalidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ class CacheTagInvalidatorTest extends KernelTestBase {
protected function setUp() : void {
parent::setUp();

$this->config('helfi_api_base.pubsub.settings')
->set('endpoint', 'wss://localhost')
->set('hub', 'hub')
->set('group', 'group')
->set('access_key', '123')
$this->config('helfi_api_base.api_accounts')
->set('vault', [
[
'id' => 'pubsub',
'plugin' => 'json',
'data' => json_encode([
'endpoint' => 'localhost',
'hub' => 'local',
'group' => 'invalidate_cache',
'access_key' => '123',
]),
],
])
->save();
}

Expand Down
Loading

0 comments on commit 834d8ba

Please sign in to comment.