From ca5aed22ac0d36daefb4370586ad60d772b93f2e Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 11:24:31 +0300 Subject: [PATCH 01/15] UHF-10012: Simplify api and vault accounts --- config/install/helfi_api_base.features.yml | 1 + .../rest.resource.helfi_debug_data.yml | 16 -- config/schema/helfi_api_base.schema.yml | 4 +- documentation/api-accounts.md | 89 +++---- helfi_api_base.install | 133 +++------- helfi_api_base.module | 6 + helfi_api_base.services.yml | 1 + src/Commands/ApiAccountCommands.php | 250 ------------------ src/Features/FeatureManager.php | 6 +- .../rest/resource/DebugDataResource.php | 95 +------ 10 files changed, 88 insertions(+), 513 deletions(-) delete mode 100644 config/optional/rest.resource.helfi_debug_data.yml delete mode 100644 src/Commands/ApiAccountCommands.php diff --git a/config/install/helfi_api_base.features.yml b/config/install/helfi_api_base.features.yml index f53dda82..84a6997a 100644 --- a/config/install/helfi_api_base.features.yml +++ b/config/install/helfi_api_base.features.yml @@ -1 +1,2 @@ disable_user_password: true +user_expire: true diff --git a/config/optional/rest.resource.helfi_debug_data.yml b/config/optional/rest.resource.helfi_debug_data.yml deleted file mode 100644 index e845e3b5..00000000 --- a/config/optional/rest.resource.helfi_debug_data.yml +++ /dev/null @@ -1,16 +0,0 @@ -langcode: en -status: true -dependencies: - module: - - user -id: helfi_debug_data -plugin_id: 'helfi_debug_data' -granularity: resource -configuration: - methods: - - GET - formats: - - json - authentication: - - cookie - - basic_auth diff --git a/config/schema/helfi_api_base.schema.yml b/config/schema/helfi_api_base.schema.yml index eb4f6298..488f7a29 100644 --- a/config/schema/helfi_api_base.schema.yml +++ b/config/schema/helfi_api_base.schema.yml @@ -33,10 +33,10 @@ helfi_api_base.api_accounts: helfi_api_base.features: type: config_entity mapping: - logger: - type: boolean disable_user_password: type: boolean + user_expire: + type: boolean helfi_api_base.environment_resolver.settings: type: config_entity diff --git a/documentation/api-accounts.md b/documentation/api-accounts.md index 69d2ad8a..4a7aa1c3 100644 --- a/documentation/api-accounts.md +++ b/documentation/api-accounts.md @@ -25,41 +25,36 @@ $config['helfi_api_base.api_accounts']['accounts'][] = [ If no `mail` is provided, an autogenerated email address like `drupal+$username@hel.fi` is used. For example: `drupal+account1@hel.fi`. -### Using environment variable to define accounts +### Configuring API accounts on OpenShift -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`. +Add new secret to your project's KeyVault on Azure Portal. -The value should be a base64 encoded JSON string of whatever is defined in `helfi_api_base.api_accounts.accounts` configuration, for example: +For example, add a new secret called `YOUR-API-ACCOUNT`. This will be automatically mapped to an env variable called `YOUR_API_ACCOUNT`. -```bash -php -r "print base64_encode('[{"username":"account1","password":"password1","roles":["role1","role2"]},{"username":"account2","password":"password2","mail":"some-email@example.com"}]');" +The value should be a JSON encoded string, something like: +```json +{ + "username": "account1", + "password": "password1", + "roles": ["role1"], + "mail": "some-email@example.com" +} ``` -Then map the given output to `DRUPAL_API_ACCOUNTS` environment variable: -```bash -DRUPAL_API_ACCOUNTS=W3t1c2VybmFtZTphY2NvdW50MSxwYXNzd29yZDpwYXNzd29yZDEscm9sZXM6W3JvbGUxLHJvbGUyXX0se3VzZXJuYW1lOmFjY291bnQyLHBhc3N3b3JkOnBhc3N3b3JkMixtYWlsOnNvbWUtZW1haWxAZXhhbXBsZS5jb219XQ== +Add mapping to your project's `all.settings.php`: +```php +# public/sites/all.settings.php + +if ($your_api_account = getenv('YOUR_API_ACCOUNT')) { + $config['helfi_api_base.api_accounts']['accounts'][] = json_decode($your_api_account, TRUE); +} ``` ### 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) -### Testing locally - -Add something like this to your `local.settings.php`: - -```php -# local.settings.php -$api_accounts = [ - [ - 'username' => 'helfi-debug-data', - 'password' => '123', - 'mail' => 'drupal+debug_api@hel.fi', - 'roles' => ['debug_api'], - ], -]; -$config['helfi_api_base.api_accounts']['accounts'] = $api_accounts; -``` +You can test this locally by running `drush helfi:post-deploy`. ## Managing external API credentials @@ -90,20 +85,22 @@ The value of `data` field depends on used `plugin`: - 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 +### Configuring Vault accounts on OpenShift -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`. +Add new secret to your project's KeyVault on Azure Portal. -The value should be a base64 encoded JSON string of whatever is defined in `helfi_api_base.api_accounts.vault` configuration, for example: +For example, add a new secret called `YOUR-VAULT-ACCOUNT`. This will be automatically mapped to an env variable called `YOUR_VAULT_ACCOUNT`. -```bash -php -r "print base64_encode('[{"id": "global_navigation", "plugin": "authorization_token": "data": "aGVsZmktYWRtaW46MTIz"}]');" -``` +Add mapping to your project's `all.settings.php` file, or `settings.php` if the feature should be enabled everywhere by default: -Then map the given output to `DRUPAL_VAULT_ACCOUNTS` environment variable: - -```bash -DRUPAL_VAULT_ACCOUNTS=W3tpZDogZXR1c2l2dV9sb2NhbCwgcGx1Z2luOiBhdXRob3JpemF0aW9uX3Rva2VuOiBkYXRhOiBhR1ZzWm1rdFlXUnRhVzQ2TVRJen1d +```php +if ($your_vault_account = getenv('YOUR_VAULT_ACCOUNT')) { + $config['helfi_api_base.api_accounts']['vault'][] = [ + 'id' => 'your_vault_account', + 'plugin' => 'authorization_token', + 'data' => $your_vault_account, + ]; +} ``` ### Usage @@ -112,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('global_navigation'); // 'global_navigation' is the ID previously defined in DRUPAL_VAULT_ACCOUNTS. -$id = $item->id(); // $id = 'global_navigation'. +$item = $service->get('your_vault_account'); // 'your_vault_account' is the ID previously defined in YOUR_VAULT_ACCOUNT. +$id = $item->id(); // $id = 'vault_account_id'. $data = $item->data() // $data = 'aGVsZmktYWRtaW46MTIz'. This is a base64 encoded basic auth token (helfi-admin:123). ``` @@ -125,28 +122,10 @@ Add something like this to your `local.settings.php`: # local.settings.php $vault_accounts = [ [ - 'id' => 'etusivu_local', + 'id' => 'your_vault_account', 'plugin' => 'authorization_token', 'data' => base64_encode('helfi-debug-data:123'), ], ]; $config['helfi_api_base.api_accounts']['vault'] = $vault_accounts; ``` - -## Tool to create/update the secret - -See https://helsinkisolutionoffice.atlassian.net/wiki/spaces/HEL/pages/6785826654/Ymp+rist+t (in Finnish) for more information on how to actually update the value for given environment variable. - -This module provides a Drush command to easily update and create API secrets. The command returns a base64 encoded string that can directly be copied to Azure Key Vault. - -### Update - -Use `drush helfi:update-api-secret` command to update the API secret. - -### Create - -Use `drush helfi:create-api-secret` command to create the API secret. - -### Show current value - -Call `drush helfi:reveal-api-secret {secret value}` to show the value of given secret. diff --git a/helfi_api_base.install b/helfi_api_base.install index 224b550b..dc1b28e0 100644 --- a/helfi_api_base.install +++ b/helfi_api_base.install @@ -11,6 +11,7 @@ use Drupal\Core\Config\FileStorage; use Drupal\helfi_api_base\Features\FeatureManager; use Drupal\rest\Entity\RestResourceConfig; use Drupal\user\Entity\Role; +use Drupal\user\Entity\User; /** * Implements hook_install(). @@ -34,31 +35,6 @@ function helfi_api_base_install(bool $is_syncing = FALSE) : void { } } - /** @var \Drupal\helfi_api_base\Environment\EnvironmentResolverInterface $envResolver */ - $envResolver = \Drupal::service('helfi_api_base.environment_resolver'); - - try { - // Only create the role if we have a valid project. This should - // only be run for projects defined in environments.json - // file. - $envResolver->getActiveProject(); - - // Make sure 'rest' module is enabled. - if (!Drupal::moduleHandler()->moduleExists('rest')) { - Drupal::service('module_installer')->install([ - 'rest', - ]); - } - - if (!$role = Role::load('debug_api')) { - $role = Role::create(['label' => 'Debug API', 'id' => 'debug_api']); - } - $role->grantPermission('restful get helfi_debug_data') - ->save(); - } - catch (\InvalidArgumentException) { - } - if (Drupal::moduleHandler()->moduleExists('raven')) { Drupal::configFactory()->getEditable('raven.settings') ->set('drush_error_handler', TRUE) @@ -77,24 +53,6 @@ function helfi_api_base_update_9001() : void { helfi_api_base_install(); } -/** - * Install the debug rest config. - */ -function helfi_api_base_update_9002() : void { - if (!Drupal::moduleHandler()->moduleExists('rest')) { - return; - } - /** @var \Drupal\Core\Extension\ExtensionPathResolver $extensionPathResolver */ - $extensionPathResolver = Drupal::service('extension.path.resolver'); - $config_path = $extensionPathResolver->getPath('module', 'helfi_api_base') . '/config/optional'; - $source = new FileStorage($config_path); - $config_storage = Drupal::service('config.storage'); - - // Install rest resource config. - $config_name = 'rest.resource.helfi_debug_data'; - $config_storage->write($config_name, $source->read($config_name)); -} - /** * Install the package version rest config. */ @@ -116,34 +74,6 @@ function helfi_api_base_update_9003() : void { } } -/** - * Disable syslog module and enable filelog. - */ -function helfi_api_base_update_9004() : void { - Drupal::service('module_installer')->uninstall([ - 'syslog', - ]); - Drupal::service('module_installer')->install([ - 'filelog', - ]); -} - -/** - * Disable filelog module, enable log_stdout module. - */ -function helfi_api_base_update_9005() : void { - Drupal::service('module_installer')->uninstall([ - 'filelog', - ]); -} - -/** - * Re-run 9005 update. - */ -function helfi_api_base_9006() : void { - helfi_api_base_update_9005(); -} - /** * Enable health check. */ @@ -165,7 +95,6 @@ function helfi_api_base_update_9009() : void { ]); $endpoints = RestResourceConfig::loadMultiple([ - 'helfi_debug_data', 'helfi_debug_package_version', ]); foreach ($endpoints as $endpoint) { @@ -191,23 +120,14 @@ function helfi_api_base_update_9013() : void { * Enable 'delete old revisions' feature. */ function helfi_api_base_update_9014() : void { - /** @var \Drupal\helfi_api_base\Environment\EnvironmentResolverInterface $service */ - $service = \Drupal::service('helfi_api_base.environment_resolver'); - try { - $service->getActiveProject(); - - \Drupal::configFactory()->getEditable('helfi_api_base.delete_revisions') - ->set('entity_types', [ - 'node', - 'paragraph', - 'tpr_unit', - 'tpr_service', - 'tpr_errand_service', - ])->save(); - } - catch (\InvalidArgumentException) { - } - + \Drupal::configFactory()->getEditable('helfi_api_base.delete_revisions') + ->set('entity_types', [ + 'node', + 'paragraph', + 'tpr_unit', + 'tpr_service', + 'tpr_errand_service', + ])->save(); } /** @@ -216,15 +136,7 @@ function helfi_api_base_update_9014() : void { function helfi_api_base_update_9015() : void { /** @var \Drupal\helfi_api_base\Features\FeatureManager $service */ $service = \Drupal::service(FeatureManager::class); - /** @var \Drupal\helfi_api_base\Environment\EnvironmentResolverInterface $environmentResolver */ - - $environmentResolver = \Drupal::service('helfi_api_base.environment_resolver'); - try { - $environmentResolver->getActiveProject(); - $service->enableFeature(FeatureManager::DISABLE_USER_PASSWORD); - } - catch (\InvalidArgumentException) { - } + $service->enableFeature(FeatureManager::DISABLE_USER_PASSWORD); } /** @@ -251,3 +163,28 @@ function helfi_api_base_update_9017(): void { 'monolog', ]); } + +/** + * Enable user expire feature. + */ +function helfi_api_base_update_9018(): void { + /** @var \Drupal\helfi_api_base\Features\FeatureManager $service */ + $service = \Drupal::service(FeatureManager::class); + $service->enableFeature(FeatureManager::USER_EXPIRE); +} + +/** + * Remove debug_api role and debug api functionality. + */ +function helfi_api_base_update_9019(): void { + if ($account = user_load_by_name('helfi-debug-data')) { + $account->delete(); + } + + if ($role = Role::load('debug_api')) { + $role->delete(); + } + // Delete rest endpoint configuration. + \Drupal::configFactory()->getEditable('rest.resource.helfi_debug_data') + ->delete(); +} diff --git a/helfi_api_base.module b/helfi_api_base.module index 4da42b9a..557ca41a 100644 --- a/helfi_api_base.module +++ b/helfi_api_base.module @@ -88,3 +88,9 @@ function helfi_api_base_entity_update(EntityInterface $entity) : void { ]); } } + +/** + * Implements hook_cron(). + */ +function helfi_api_base_cron() : void { +} diff --git a/helfi_api_base.services.yml b/helfi_api_base.services.yml index e7c4e5e7..dc428794 100644 --- a/helfi_api_base.services.yml +++ b/helfi_api_base.services.yml @@ -24,6 +24,7 @@ services: parent: logger.channel_base arguments: [ 'helfi_api_base' ] + Drupal\helfi_api_base\UserExpire\UserExpireManager: ~ Drupal\helfi_api_base\EventSubscriber\SentryTracesSamplerSubscriber: ~ Drupal\helfi_api_base\EventSubscriber\DisableUserPasswordSubscriber: ~ Drupal\helfi_api_base\Features\FeatureManager: ~ diff --git a/src/Commands/ApiAccountCommands.php b/src/Commands/ApiAccountCommands.php deleted file mode 100644 index f772743d..00000000 --- a/src/Commands/ApiAccountCommands.php +++ /dev/null @@ -1,250 +0,0 @@ -io()->note('Current value:'); - $this->io()->writeln(json_encode($currentValue, flags: JSON_PRETTY_PRINT)); - - return DrushCommands::EXIT_SUCCESS; - } - - /** - * Updates the base64 and json encoded Azure secret. - * - * @param array $options - * The options. - * - * @return int - * The exit code. - * - * @throws \JsonException - */ - #[Command(name: 'helfi:update-api-secret')] - #[Option(name: 'filename', description: 'An optional filename to read the secret from.')] - #[Option(name: 'type', description: 'The type of given secret. [1 = DRUPAL_VAULT_ACCOUNTS or 2 = DRUPAL_API_ACCOUNTS')] - #[Option(name: 'id', description: 'An unique ID for given item.')] - #[Option(name: 'plugin', description: 'The plugin')] - #[Option(name: 'data', description: 'The data.')] - public function update( - array $options = [ - 'file' => NULL, - 'type' => NULL, - 'id' => NULL, - 'plugin' => NULL, - 'data' => NULL, - ], - ) : int { - $type = $this->askType($options['type']); - - if ($options['file']) { - $value = file_get_contents($options['file']); - } - else { - $value = $this->io()->ask('The base64 and JSON encoded secret value'); - - if ($value && strlen($value) >= 4095) { - throw new \InvalidArgumentException('The secret value is longer than 4096 bytes and will be truncated by your terminal. Use --file to pass a file to read the content from instead.'); - } - } - if (!$value) { - throw new \InvalidArgumentException('No value given.'); - } - - $values = json_decode(base64_decode(trim($value)), TRUE, flags: JSON_THROW_ON_ERROR); - - $this->io() - ->note(sprintf('Current value: %s', json_encode($values, flags: JSON_PRETTY_PRINT))); - - $values = array_merge($values, [$this->processValues($type, $options)]); - - $this->io()->note(sprintf('New value: %s', json_encode($values))); - $this->io() - ->writeln( - vsprintf('Copy paste this value to Azure Key vault [%s]: %s', [ - $type, - base64_encode(json_encode($values)), - ]) - ); - - return DrushCommands::EXIT_SUCCESS; - } - - /** - * Creates a base64 and json encoded Azure secret. - * - * @return int - * The exit code. - */ - #[Command(name: 'helfi:create-api-secret')] - public function create() : int { - $type = $this->askType(); - - $values = [$this->processValues($type)]; - - $this->io()->note(sprintf('The value: %s', json_encode($values))); - $this->io() - ->writeln( - vsprintf('Copy paste this value to Azure Key vault [%s]: %s', [ - $type, - base64_encode(json_encode($values)), - ]) - ); - return DrushCommands::EXIT_SUCCESS; - } - - /** - * Prompts the user for the secret type. - * - * @param string|null $type - * The type. - * - * @return string - * The secret type. - */ - private function askType(string $type = NULL) : string { - if (!$type) { - $type = $this->io() - ->ask('The type of given secret [1 = DRUPAL_VAULT_ACCOUNTS or 2 = DRUPAL_API_ACCOUNTS]', '2'); - } - - return match($type) { - '1' => 'DRUPAL_VAULT_ACCOUNTS', - '2' => 'DRUPAL_API_ACCOUNTS', - default => throw new \InvalidArgumentException('Invalid secret type'), - }; - } - - /** - * Processes the field values. - * - * @param string $type - * The secret type. - * @param array $options - * The default options. - * - * @return array - * The processed field values. - */ - private function processValues(string $type, array $options = []) : array { - $values = []; - - foreach ($this->getFields($type) as $name => $option) { - [ - 'description' => $description, - 'default_value' => $defaultValue, - 'callback' => $callback, - ] = $option; - - if (!$callback) { - $callback = function ($value) { - return trim($value); - }; - } - - if ((!isset($options[$name])) || is_null($options[$name])) { - $options[$name] = $this->io() - ->ask(sprintf('Provide %s [%s]', $name, $description), $defaultValue); - } - $value = $callback($options[$name]); - - if (!$value) { - continue; - } - $values[$name] = $value; - } - return $values; - } - - /** - * Gets the fields for given type. - * - * @param string $type - * The type. - * - * @return array[] - * The fields. - */ - private function getFields(string $type) : array { - return match($type) { - 'DRUPAL_VAULT_ACCOUNTS' => [ - 'id' => [ - 'description' => 'An unique ID for given item', - 'default_value' => '', - 'callback' => NULL, - ], - 'plugin' => [ - 'description' => 'The plugin', - 'default_value' => 'authorization_token', - 'callback' => NULL, - ], - 'data' => [ - 'description' => 'The data. An authorization token or basic auth string for example.', - 'default_value' => '', - 'callback' => NULL, - ], - ], - 'DRUPAL_API_ACCOUNTS' => [ - 'username' => [ - 'description' => 'The username', - 'default_value' => '', - 'callback' => NULL, - ], - 'password' => [ - 'description' => 'The password. Leave empty to generate a random password.', - 'default_value' => NULL, - 'callback' => function (?string $value) : string { - return $value ?: $this->passwordGenerator->generate(30); - }, - ], - 'mail' => [ - 'description' => 'Leave empty to use automatically generated email', - 'default_value' => '', - 'callback' => NULL, - ], - 'roles' => [ - 'description' => 'A comma separated list of roles', - 'default_value' => '', - 'callback' => function (string $value) : array { - return array_map('trim', explode(',', $value)); - }, - ], - ], - }; - } - -} diff --git a/src/Features/FeatureManager.php b/src/Features/FeatureManager.php index a24055e6..95774fe9 100644 --- a/src/Features/FeatureManager.php +++ b/src/Features/FeatureManager.php @@ -12,6 +12,7 @@ final class FeatureManager { public const DISABLE_USER_PASSWORD = 'disable_user_password'; + public const USER_EXPIRE = 'user_expire'; /** * Constructs a new instance. @@ -85,7 +86,10 @@ public function disableFeature(string $feature) : void { } /** - * {@inheritdoc} + * Checks if the given feature is enabled. + * + * @return bool + * TRUE if feature is enabled, FALSE if not. */ public function isEnabled(string $feature) : bool { $this->assertFeature($feature); diff --git a/src/Plugin/rest/resource/DebugDataResource.php b/src/Plugin/rest/resource/DebugDataResource.php index a4cec335..e9af0ff5 100644 --- a/src/Plugin/rest/resource/DebugDataResource.php +++ b/src/Plugin/rest/resource/DebugDataResource.php @@ -4,17 +4,14 @@ namespace Drupal\helfi_api_base\Plugin\rest\resource; -use Drupal\Component\Plugin\DependentPluginInterface; -use Drupal\Core\Cache\CacheableDependencyInterface; -use Drupal\Core\Cache\CacheableMetadata; -use Drupal\helfi_api_base\DebugDataItemPluginManager; use Drupal\rest\Plugin\ResourceBase; use Drupal\rest\ResourceResponse; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Represents Debug records as resources. * + * @todo Remove this once we have removed it from everything else. + * * @RestResource ( * id = "helfi_debug_data", * label = @Translation("Debug data"), @@ -23,52 +20,7 @@ * } * ) */ -final class DebugDataResource extends ResourceBase implements DependentPluginInterface { - - /** - * The debug data plugin manager. - * - * @var \Drupal\helfi_api_base\DebugDataItemPluginManager - */ - private DebugDataItemPluginManager $manager; - - /** - * The list of initialized plugins. - * - * @var \Drupal\helfi_api_base\DebugDataItemInterface[] - */ - private array $debugDataPlugins = []; - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : self { - $instance = parent::create( - $container, - $configuration, - $plugin_id, - $plugin_definition - ); - $instance->manager = $container->get('plugin.manager.debug_data_item'); - - return $instance; - } - - /** - * Initializes debug data plugins. - * - * @return \Drupal\helfi_api_base\DebugDataItemInterface[] - * The list of debug data plugins. - */ - private function getDataPlugins() : array { - if (!$this->debugDataPlugins) { - foreach ($this->manager->getDefinitions() as $definition) { - $this->debugDataPlugins[$definition['id']] = $this->manager - ->createInstance($definition['id']); - } - } - return $this->debugDataPlugins; - } +final class DebugDataResource extends ResourceBase { /** * Responds to GET requests. @@ -79,46 +31,7 @@ private function getDataPlugins() : array { * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function get() : ResourceResponse { - $cacheableMetadata = new CacheableMetadata(); - - $data = []; - foreach ($this->getDataPlugins() as $id => $instance) { - $data[$id] = [ - 'label' => $instance->label(), - 'data' => $instance->collect(), - ]; - - if ($instance instanceof CacheableDependencyInterface) { - $cacheableMetadata->addCacheableDependency($instance); - } - } - return (new ResourceResponse($data)) - ->addCacheableDependency($cacheableMetadata); - } - - /** - * {@inheritdoc} - */ - public function calculateDependencies() : array { - $dependencies = [ - 'module' => ['user'], - ]; - - foreach ($this->getDataPlugins() as $plugin) { - foreach ($plugin->calculateDependencies() as $type => $value) { - if (!isset($dependencies[$type])) { - $dependencies[$type] = []; - } - // Merge existing dependencies together, make them unique and - // reindex the dependency list. - $dependencies[$type] = array_values( - array_unique( - array_merge($dependencies[$type], $value) - ) - ); - } - } - return $dependencies; + return new ResourceResponse([]); } } From ee03f8162cd46cecec78e24086f69a793fab67f6 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 11:36:52 +0300 Subject: [PATCH 02/15] UHF-10012: Fixed phpcs --- helfi_api_base.install | 1 - src/UserExpire/UserExpireManager.php | 85 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/UserExpire/UserExpireManager.php diff --git a/helfi_api_base.install b/helfi_api_base.install index dc1b28e0..92f8f5d1 100644 --- a/helfi_api_base.install +++ b/helfi_api_base.install @@ -11,7 +11,6 @@ use Drupal\Core\Config\FileStorage; use Drupal\helfi_api_base\Features\FeatureManager; use Drupal\rest\Entity\RestResourceConfig; use Drupal\user\Entity\Role; -use Drupal\user\Entity\User; /** * Implements hook_install(). diff --git a/src/UserExpire/UserExpireManager.php b/src/UserExpire/UserExpireManager.php new file mode 100644 index 00000000..e2eb3d67 --- /dev/null +++ b/src/UserExpire/UserExpireManager.php @@ -0,0 +1,85 @@ +entityTypeManager->getStorage('user'); + + foreach ($this->getExpiredUserIds() as $uid) { + /** @var \Drupal\user\UserInterface $user */ + if (!$user = $storage->load($uid)) { + continue; + } + $user->block() + ->save(); + } + } + + /** + * Gets the expired user ids. + * + * @return array + * An array of user ids. + */ + public function getExpiredUserIds() : array { + $expireDate = ($this->time->getCurrentTime() - self::DEFAULT_EXPIRE); + + $query = $this->entityTypeManager->getStorage('user') + ->getQuery(); + // Load users that have logged in at some point (access > 0), but + // the access time is less than (current time - expire time). + $accessCondition = $query->andConditionGroup() + ->condition('access', 0, '>') + ->condition('access', $expireDate, '<='); + // Load users that have never logged in (access=0), and the + // created time is less than (current time - expire time). + $createdCondition = $query->andConditionGroup() + ->condition('access', 0) + ->condition('created', $expireDate, '<='); + $expireCondition = $query->orConditionGroup() + ->condition($accessCondition) + ->condition($createdCondition); + + $query + ->condition($expireCondition) + ->condition('status', 1) + ->accessCheck(FALSE) + // Make sure we have an upper bound. + ->range(0, 50); + + return $query->execute(); + } + +} From b0e1d19ddfe405a801bdece2e9770104f61b2d79 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 12:07:24 +0300 Subject: [PATCH 03/15] UHF-10012: Removed debug data resource test --- tests/src/Unit/DebugDataResourceTest.php | 166 ----------------------- 1 file changed, 166 deletions(-) delete mode 100644 tests/src/Unit/DebugDataResourceTest.php diff --git a/tests/src/Unit/DebugDataResourceTest.php b/tests/src/Unit/DebugDataResourceTest.php deleted file mode 100644 index 2d67c6b9..00000000 --- a/tests/src/Unit/DebugDataResourceTest.php +++ /dev/null @@ -1,166 +0,0 @@ -dependencies; - } - - /** - * {@inheritdoc} - */ - public function getCacheContexts() : array { - return []; - } - - /** - * {@inheritdoc} - */ - public function getCacheTags() : array { - return []; - } - - /** - * {@inheritdoc} - */ - public function getCacheMaxAge() : int { - return -1; - } - - }; - } - - /** - * Gets the sut. - * - * @return \Drupal\helfi_api_base\Plugin\rest\resource\DebugDataResource - * The sut. - */ - private function getSut() : DebugDataResource { - $pluginManager = $this->prophesize(DebugDataItemPluginManager::class); - $pluginManager->getDefinitions() - ->willReturn([ - ['id' => 'test', 'label' => new TranslatableMarkup('Test')], - ['id' => 'test2', 'label' => 'Test 2'], - ]); - $pluginManager->createInstance('test') - ->willReturn($this->getDataItemPlugin([ - 'config' => ['user.role.anonymous'], - 'theme' => ['seven'], - 'content' => ['node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'], - 'module' => ['node'], - ])); - $pluginManager->createInstance('test2') - ->willReturn($this->getDataItemPlugin([ - 'config' => ['user.role.authenticated'], - 'module' => ['helfi_api_base'], - ])); - $logger = $this->prophesize(LoggerInterface::class); - $loggerFactory = $this->prophesize(LoggerChannelFactoryInterface::class); - $loggerFactory->get('rest') - ->willReturn($logger->reveal()); - $container = new ContainerBuilder(); - $container->setParameter('serializer.formats', ['json']); - $container->set('logger.factory', $loggerFactory->reveal()); - $container->set('plugin.manager.debug_data_item', $pluginManager->reveal()); - - return DebugDataResource::create($container, [], 'plugin', []); - } - - /** - * Tests that dependencies are populated. - * - * @covers ::calculateDependencies - * @covers ::create - * @covers ::getDataPlugins - */ - public function testDependencies() : void { - // Make sure multiple dependencies are merged together. - $this->assertEquals([ - 'config' => ['user.role.anonymous', 'user.role.authenticated'], - 'theme' => ['seven'], - 'content' => ['node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'], - 'module' => ['user', 'node', 'helfi_api_base'], - ], $this->getSut()->calculateDependencies()); - } - - /** - * Tests that cacheable metadata is added. - * - * @covers ::get - * @covers ::create - * @covers ::getDAtaPlugins - */ - public function testCacheableMetadata() : void { - $response = $this->getSut()->get(); - $this->assertInstanceOf(ResourceResponse::class, $response); - $this->assertEquals( - CacheBackendInterface::CACHE_PERMANENT, - $response->getCacheableMetadata()->getCacheMaxAge() - ); - } - -} From 26d6361c23111b589581428b21341e51c923d4f4 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 12:08:55 +0300 Subject: [PATCH 04/15] UHF-10012: phpstan fixes --- src/UserExpire/UserExpireManager.php | 2 +- .../src/Kernel/UserExpire/UserExpireTest.php | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tests/src/Kernel/UserExpire/UserExpireTest.php diff --git a/src/UserExpire/UserExpireManager.php b/src/UserExpire/UserExpireManager.php index e2eb3d67..595d36b0 100644 --- a/src/UserExpire/UserExpireManager.php +++ b/src/UserExpire/UserExpireManager.php @@ -58,6 +58,7 @@ public function getExpiredUserIds() : array { $query = $this->entityTypeManager->getStorage('user') ->getQuery(); + $query->accessCheck(FALSE); // Load users that have logged in at some point (access > 0), but // the access time is less than (current time - expire time). $accessCondition = $query->andConditionGroup() @@ -75,7 +76,6 @@ public function getExpiredUserIds() : array { $query ->condition($expireCondition) ->condition('status', 1) - ->accessCheck(FALSE) // Make sure we have an upper bound. ->range(0, 50); diff --git a/tests/src/Kernel/UserExpire/UserExpireTest.php b/tests/src/Kernel/UserExpire/UserExpireTest.php new file mode 100644 index 00000000..289f9a7c --- /dev/null +++ b/tests/src/Kernel/UserExpire/UserExpireTest.php @@ -0,0 +1,94 @@ +installEntitySchema('user'); + $this->installConfig(['helfi_api_base']); + } + + /** + * Gets the SUT. + * + * @return \Drupal\helfi_api_base\UserExpire\UserExpireManager + * The SUT. + */ + public function getSut() : UserExpireManager { + return $this->container->get(UserExpireManager::class); + } + + /** + * Tests the expired users. + */ + public function testExpiredUsers() : void { + /** @var \Drupal\user\UserInterface[] $users */ + $users = [ + '1' => $this->createUser(), + '2' => $this->createUser(), + '3' => $this->createUser(), + ]; + + foreach ($users as $user) { + // Make sure users have never logged in. + $this->assertEquals(0, $user->getLastAccessedTime()); + $this->assertTrue($user->getCreatedTime() > 0); + } + + $expired = $this->getSut()->getExpiredUserIds(); + $this->assertEmpty($expired); + + // Set access time below the threshold. + $users['1']->setLastAccessTime(strtotime('-1 months')) + ->save(); + // Set access time over the threshold. + $users['2']->setLastAccessTime(strtotime('-7 months')) + ->save(); + + $expired = $this->getSut()->getExpiredUserIds(); + $this->assertEquals([2 => 2], $expired); + + // Set created time over the threshold. + $users['3']->set('created', strtotime('-7 months')) + ->save(); + + $expired = $this->getSut()->getExpiredUserIds(); + $this->assertEquals([2 => 2, 3 => 3], $expired); + + $this->getSut()->cancelExpiredUsers(); + + // Users 2 and 3 should be blocked. + $this->assertFalse(User::load(1)->isBlocked()); + $this->assertTrue(User::load(2)->isBlocked()); + $this->assertTrue(User::load(3)->isBlocked()); + } + +} From 4a8ee5e1393c70fe525d54715ee97682e0b8b353 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 12:53:21 +0300 Subject: [PATCH 05/15] UHF-10012: Use different permission for debug route --- helfi_api_base.routing.yml | 2 +- tests/src/Functional/DebugUiTest.php | 32 +------ tests/src/Functional/InstallTest.php | 86 ------------------- .../Kernel/Controller/DebugControllerTest.php | 2 +- 4 files changed, 3 insertions(+), 119 deletions(-) delete mode 100644 tests/src/Functional/InstallTest.php diff --git a/helfi_api_base.routing.yml b/helfi_api_base.routing.yml index cb51d2cb..3dc54f8f 100644 --- a/helfi_api_base.routing.yml +++ b/helfi_api_base.routing.yml @@ -20,4 +20,4 @@ helfi_api_base.debug_list: _title: 'Debug' _controller: '\Drupal\helfi_api_base\Controller\DebugController::build' requirements: - _permission: 'restful get helfi_debug_data' + _permission: 'restful get helfi_debug_package_version' diff --git a/tests/src/Functional/DebugUiTest.php b/tests/src/Functional/DebugUiTest.php index 360ce2c2..86888d1d 100644 --- a/tests/src/Functional/DebugUiTest.php +++ b/tests/src/Functional/DebugUiTest.php @@ -4,8 +4,6 @@ namespace Drupal\Tests\helfi_api_base\Functional; -use Drupal\user\Entity\Role; - /** * Tests debug data rest resource. * @@ -27,34 +25,6 @@ class DebugUiTest extends MigrationTestBase { */ protected $defaultTheme = 'stark'; - /** - * Tests /debug endpoint. - */ - public function testDebugEndpoint() : void { - $this->drupalGet('/api/v1/debug'); - $this->assertSession()->statusCodeEquals(403); - - // Allow all users to fetch resources. - Role::load('anonymous') - ->grantPermission('restful get helfi_debug_data') - ->save(); - - $this->drupalGet('/api/v1/debug'); - $this->assertSession()->statusCodeEquals(200); - $content = json_decode($this->getSession()->getPage()->getContent(), TRUE); - - $this->assertNotEmpty($content['composer']); - // Make sure we have no imported items by default. - $this->assertEquals(0, $content['migrate']['data'][0]['imported']); - // Run migration to verify that caches are cleared properly. - $this->executeMigration('dummy_migrate'); - - $this->drupalGet('/api/v1/debug'); - $this->assertSession()->statusCodeEquals(200); - $content = json_decode($this->getSession()->getPage()->getContent(), TRUE); - $this->assertEquals(4, $content['migrate']['data'][0]['imported']); - } - /** * Tests the admin UI route. */ @@ -62,7 +32,7 @@ public function testDebugAdminUi() : void { $this->drupalGet('/admin/debug'); $this->assertSession()->statusCodeEquals(403); - $account = $this->createUser(['restful get helfi_debug_data']); + $account = $this->createUser(['restful get helfi_debug_package_version']); $this->drupalLogin($account); $this->drupalGet('/admin/debug'); diff --git a/tests/src/Functional/InstallTest.php b/tests/src/Functional/InstallTest.php deleted file mode 100644 index b0fe87ce..00000000 --- a/tests/src/Functional/InstallTest.php +++ /dev/null @@ -1,86 +0,0 @@ -config('helfi_api_base.api_accounts') - ->set('accounts', [ - [ - 'username' => 'helfi-admin', - 'password' => '123', - 'roles' => ['debug_api'], - ], - ]) - ->save(); - } - catch (SchemaIncompleteException) { - } - } - - /** - * Make sure debug api role is created when accounts are defined. - */ - public function testInstall() : void { - /** @var \Drupal\Core\Extension\ModuleInstaller $moduleInstaller */ - $moduleInstaller = $this->container->get('module_installer'); - // Enable the 'helfi_api_base' module to trigger hook_install(). - $moduleInstaller->install(['helfi_api_base']); - // Make sure 'debug_api' role is not created when active project is not - // defined. - $this->assertNull(Role::load('debug_api')); - - // Re-install the module to make sure 'debug_api' role is created and - // required permissions are granted when active environment is defined. - $moduleInstaller->uninstall(['helfi_api_base']); - - try { - $this->setActiveProject(Project::ASUMINEN, EnvironmentEnum::Local); - } - catch (SchemaIncompleteException) { - } - $moduleInstaller->install(['helfi_api_base']); - - /** @var \Drupal\user\Entity\Role $role */ - $role = Role::load('debug_api'); - $this->assertTrue($role->hasPermission('restful get helfi_debug_data')); - } - -} diff --git a/tests/src/Kernel/Controller/DebugControllerTest.php b/tests/src/Kernel/Controller/DebugControllerTest.php index a24c62e2..c09cd058 100644 --- a/tests/src/Kernel/Controller/DebugControllerTest.php +++ b/tests/src/Kernel/Controller/DebugControllerTest.php @@ -37,7 +37,7 @@ public function testControllerPermission() : void { $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); - $this->drupalSetUpCurrentUser(permissions: ['restful get helfi_debug_data']); + $this->drupalSetUpCurrentUser(permissions: ['restful get helfi_debug_package_version']); $request = $this->getMockedRequest('/admin/debug'); $response = $this->processRequest($request); From f15fd5bd0819d44248415b339e603fcd9350ce3c Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 13:10:43 +0300 Subject: [PATCH 06/15] UHF-10012: Fixed drush services, fixed feature toggle test --- drush.services.yml | 5 ----- tests/src/Kernel/Features/FeatureToggleTest.php | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/drush.services.yml b/drush.services.yml index 609f2c43..61735ee1 100644 --- a/drush.services.yml +++ b/drush.services.yml @@ -13,11 +13,6 @@ services: - '@helfi_api_base.pubsub_manager' tags: - { name: drush.command } - helfi_api_base.api_account_commands: - class: \Drupal\helfi_api_base\Commands\ApiAccountCommands - arguments: ['@password_generator'] - tags: - - { name: drush.command } helfi_api_base.deploy_commands: class: \Drupal\helfi_api_base\Commands\DeployCommands arguments: ['@event_dispatcher'] diff --git a/tests/src/Kernel/Features/FeatureToggleTest.php b/tests/src/Kernel/Features/FeatureToggleTest.php index 8d98b61c..9f255989 100644 --- a/tests/src/Kernel/Features/FeatureToggleTest.php +++ b/tests/src/Kernel/Features/FeatureToggleTest.php @@ -80,6 +80,7 @@ public function testDefaults() : void { $this->assertEquals([ FeatureManager::DISABLE_USER_PASSWORD => TRUE, + FeatureManager::USER_EXPIRE => TRUE, ], $features); } From aef306dd315025c0ae086a011dfe337526432faa Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 13:16:30 +0300 Subject: [PATCH 07/15] UHF-10012: Fixed api account role --- .../Kernel/EventSubscriber/EnsureApiAccountsSubscriberTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/Kernel/EventSubscriber/EnsureApiAccountsSubscriberTest.php b/tests/src/Kernel/EventSubscriber/EnsureApiAccountsSubscriberTest.php index 157923b6..b12b2690 100644 --- a/tests/src/Kernel/EventSubscriber/EnsureApiAccountsSubscriberTest.php +++ b/tests/src/Kernel/EventSubscriber/EnsureApiAccountsSubscriberTest.php @@ -50,7 +50,7 @@ protected function setUp() : void { [ 'username' => 'helfi-admin', 'password' => '123', - 'roles' => ['debug_api'], + 'roles' => [], ], ]) ->save(); @@ -81,7 +81,6 @@ public function testPasswordReset(): void { $service->dispatch(new PostDeployEvent()); $account = user_load_by_name('helfi-admin'); $this->assertInstanceOf(UserInterface::class, $account); - $this->assertTrue($account->hasRole('debug_api')); $this->assertTrue($passwordHasher->check('123', $account->getPassword())); Role::create(['id' => 'test', 'label' => 'Test'])->save(); From 149532baa44a68a2b1b34d4a33aeb1fcfa1c6bb3 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 13:22:42 +0300 Subject: [PATCH 08/15] UHF-10012: User load should never fail --- src/UserExpire/UserExpireManager.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/UserExpire/UserExpireManager.php b/src/UserExpire/UserExpireManager.php index 595d36b0..3a49205c 100644 --- a/src/UserExpire/UserExpireManager.php +++ b/src/UserExpire/UserExpireManager.php @@ -38,11 +38,8 @@ public function cancelExpiredUsers() : void { $storage = $this->entityTypeManager->getStorage('user'); foreach ($this->getExpiredUserIds() as $uid) { - /** @var \Drupal\user\UserInterface $user */ - if (!$user = $storage->load($uid)) { - continue; - } - $user->block() + $storage->load($uid) + ->block() ->save(); } } From fc7b2276bd9ed3093435b52bde20700e273a74b7 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 13:53:01 +0300 Subject: [PATCH 09/15] UHF-10012: Converted email sending under feature toggle, test user expire cron --- config/install/helfi_api_base.features.yml | 1 + config/schema/helfi_api_base.schema.yml | 2 ++ helfi_api_base.install | 3 +- helfi_api_base.module | 19 +++++++--- src/Features/FeatureManager.php | 1 + ...pireTest.php => UserExpireManagerTest.php} | 35 ++++++++++++++++++- 6 files changed, 54 insertions(+), 7 deletions(-) rename tests/src/Kernel/UserExpire/{UserExpireTest.php => UserExpireManagerTest.php} (66%) diff --git a/config/install/helfi_api_base.features.yml b/config/install/helfi_api_base.features.yml index 84a6997a..e3b82ee8 100644 --- a/config/install/helfi_api_base.features.yml +++ b/config/install/helfi_api_base.features.yml @@ -1,2 +1,3 @@ disable_user_password: true user_expire: true +disable_email_sending: true diff --git a/config/schema/helfi_api_base.schema.yml b/config/schema/helfi_api_base.schema.yml index 488f7a29..05fde70e 100644 --- a/config/schema/helfi_api_base.schema.yml +++ b/config/schema/helfi_api_base.schema.yml @@ -33,6 +33,8 @@ helfi_api_base.api_accounts: helfi_api_base.features: type: config_entity mapping: + disable_email_sending: + type: boolean disable_user_password: type: boolean user_expire: diff --git a/helfi_api_base.install b/helfi_api_base.install index 92f8f5d1..1b8b2389 100644 --- a/helfi_api_base.install +++ b/helfi_api_base.install @@ -164,12 +164,13 @@ function helfi_api_base_update_9017(): void { } /** - * Enable user expire feature. + * Enable new features. */ function helfi_api_base_update_9018(): void { /** @var \Drupal\helfi_api_base\Features\FeatureManager $service */ $service = \Drupal::service(FeatureManager::class); $service->enableFeature(FeatureManager::USER_EXPIRE); + $service->enableFeature(FeatureManager::DISABLE_EMAIL_SENDING); } /** diff --git a/helfi_api_base.module b/helfi_api_base.module index 557ca41a..74dd0924 100644 --- a/helfi_api_base.module +++ b/helfi_api_base.module @@ -8,7 +8,9 @@ declare(strict_types=1); use Drupal\Core\Entity\EntityInterface; +use Drupal\helfi_api_base\Features\FeatureManager; use Drupal\helfi_api_base\Link\LinkProcessor; +use Drupal\helfi_api_base\UserExpire\UserExpireManager; /** * Implements hook_element_info_alter(). @@ -58,13 +60,12 @@ function helfi_api_base_theme_suggestions_debug_item(array $variables) : array { function helfi_api_base_mail_alter(&$message) : void { // Prevent sending email if current site/environment is known to the resolver. // Only helfi-drupal sites are affected by this change. - try { - \Drupal::service('helfi_api_base.environment_resolver') - ->getActiveEnvironment(); + /** @var \Drupal\helfi_api_base\Features\FeatureManager $featureManager */ + $featureManager = \Drupal::service(FeatureManager::class); + + if ($featureManager->isEnabled(FeatureManager::DISABLE_EMAIL_SENDING)) { $message['send'] = FALSE; } - catch (\InvalidArgumentException) { - } } /** @@ -93,4 +94,12 @@ function helfi_api_base_entity_update(EntityInterface $entity) : void { * Implements hook_cron(). */ function helfi_api_base_cron() : void { + /** @var \Drupal\helfi_api_base\Features\FeatureManager $featureManager */ + $featureManager = \Drupal::service(FeatureManager::class); + + if ($featureManager->isEnabled(FeatureManager::USER_EXPIRE)) { + /** @var \Drupal\helfi_api_base\UserExpire\UserExpireManager $userExpireManager */ + $userExpireManager = \Drupal::service(UserExpireManager::class); + $userExpireManager->cancelExpiredUsers(); + } } diff --git a/src/Features/FeatureManager.php b/src/Features/FeatureManager.php index 95774fe9..308d3aea 100644 --- a/src/Features/FeatureManager.php +++ b/src/Features/FeatureManager.php @@ -13,6 +13,7 @@ final class FeatureManager { public const DISABLE_USER_PASSWORD = 'disable_user_password'; public const USER_EXPIRE = 'user_expire'; + public const DISABLE_EMAIL_SENDING = 'disable_email_sending'; /** * Constructs a new instance. diff --git a/tests/src/Kernel/UserExpire/UserExpireTest.php b/tests/src/Kernel/UserExpire/UserExpireManagerTest.php similarity index 66% rename from tests/src/Kernel/UserExpire/UserExpireTest.php rename to tests/src/Kernel/UserExpire/UserExpireManagerTest.php index 289f9a7c..6f7991d3 100644 --- a/tests/src/Kernel/UserExpire/UserExpireTest.php +++ b/tests/src/Kernel/UserExpire/UserExpireManagerTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\helfi_api_base\Kernel\UserExpire; +use Drupal\helfi_api_base\Features\FeatureManager; use Drupal\helfi_api_base\UserExpire\UserExpireManager; use Drupal\KernelTests\KernelTestBase; use Drupal\Tests\user\Traits\UserCreationTrait; @@ -14,7 +15,7 @@ * * @group helfi_api_base */ -class UserExpireTest extends KernelTestBase { +class UserExpireManagerTest extends KernelTestBase { use UserCreationTrait; @@ -46,6 +47,38 @@ public function getSut() : UserExpireManager { return $this->container->get(UserExpireManager::class); } + /** + * Tests cron user removal. + */ + public function testCron() : void { + $user = $this->createUser(); + $this->assertFalse($user->isBlocked()); + $user->setLastAccessTime(strtotime('-7 months')) + ->save(); + + // Make sure the user is canceled when the cron is run. + helfi_api_base_cron(); + $this->assertTrue(User::load($user->id())->isBlocked()); + } + + /** + * Make sure the user is not blocked when the feature is disabled. + */ + public function testCronFeatureDisabled(): void { + /** @var \Drupal\helfi_api_base\Features\FeatureManager $service */ + $service = $this->container->get(FeatureManager::class); + $service->disableFeature(FeatureManager::USER_EXPIRE); + + $user = $this->createUser(); + $this->assertFalse($user->isBlocked()); + $user->setLastAccessTime(strtotime('-7 months')) + ->save(); + + // Make sure the user is canceled when the cron is run. + helfi_api_base_cron(); + $this->assertFalse(User::load($user->id())->isBlocked()); + } + /** * Tests the expired users. */ From 5034e5f369e86224efa4ec13a760fce36bb7531e Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 13:58:29 +0300 Subject: [PATCH 10/15] UHF-10012: Documentation --- README.md | 1 + documentation/disable-email-sending.md | 10 ++++++++++ documentation/disable-user-password.md | 9 +++++++++ 3 files changed, 20 insertions(+) create mode 100644 documentation/disable-email-sending.md diff --git a/README.md b/README.md index 917ab40c..d0872627 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ A base module for [drupal-helfi-platform](https://github.com/City-of-Helsinki/dr - [Default language resolver](documentation/default-languages.md): A service to handle default primary languages and language fallbacks. - [Deploy hooks](documentation/deploy-hooks.md): Allows custom tasks to be run before or after deployment. - [Disable user password](/documentation/disable-user-password.md): A deployment hook to prevent users from logging in using password. +- [Disable email sending](/documentation/disable-email-sending.md): Sending email is disabled by default. - [Environment resolver](documentation/environment-resolver.md): A service to fetch metadata for given project. - [Feature toggle](/documentation/feature-toggle.md): Allow certain functionality to be toggled on/off. - [Logging](documentation/logging.md): Log to Docker container stdout. diff --git a/documentation/disable-email-sending.md b/documentation/disable-email-sending.md new file mode 100644 index 00000000..78edebb2 --- /dev/null +++ b/documentation/disable-email-sending.md @@ -0,0 +1,10 @@ +# Disable email sending + +Email sending is disabled by default in `helfi_api_email_alter()` hook. + +To enable email sending, set `disable_email_sending` setting to false: + +```yaml +# conf/cmi/helfi_api_base.features.yml +disable_email_sending: false +``` diff --git a/documentation/disable-user-password.md b/documentation/disable-user-password.md index 5d1d8b50..650a07df 100644 --- a/documentation/disable-user-password.md +++ b/documentation/disable-user-password.md @@ -25,3 +25,12 @@ parameters: ``` or dynamically in service provider class: https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/altering-existing-services-providing-dynamic-services. + +## Disable this feature + +You can disable this feature by changing the `disable_user_password` setting to false: + +```yaml +# conf/cmi/helfi_api_base.features.yml +disable_user_password: false +``` From ece27c410af3795813fcc620993e1dacc444f7dc Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 14:07:08 +0300 Subject: [PATCH 11/15] UHF-10012: Renamed test class to match the actual class name --- .../Features/{FeatureToggleTest.php => FeatureManagerTest.php} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename tests/src/Kernel/Features/{FeatureToggleTest.php => FeatureManagerTest.php} (95%) diff --git a/tests/src/Kernel/Features/FeatureToggleTest.php b/tests/src/Kernel/Features/FeatureManagerTest.php similarity index 95% rename from tests/src/Kernel/Features/FeatureToggleTest.php rename to tests/src/Kernel/Features/FeatureManagerTest.php index 9f255989..3cd29c76 100644 --- a/tests/src/Kernel/Features/FeatureToggleTest.php +++ b/tests/src/Kernel/Features/FeatureManagerTest.php @@ -12,7 +12,7 @@ * * @group helfi_api_base */ -class FeatureToggleTest extends KernelTestBase { +class FeatureManagerTest extends KernelTestBase { /** * {@inheritdoc} @@ -80,6 +80,7 @@ public function testDefaults() : void { $this->assertEquals([ FeatureManager::DISABLE_USER_PASSWORD => TRUE, + FeatureManager::DISABLE_EMAIL_SENDING => TRUE, FeatureManager::USER_EXPIRE => TRUE, ], $features); } From 8a283eaff8f44c441c8d2196e93cf3df3b6b3f23 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 14:13:43 +0300 Subject: [PATCH 12/15] UHF-10012: User expire documentation --- README.md | 1 + documentation/user-expire.md | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 documentation/user-expire.md diff --git a/README.md b/README.md index d0872627..76d0321d 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ A base module for [drupal-helfi-platform](https://github.com/City-of-Helsinki/dr - [PubSub messaging](documentation/pubsub-messaging.md): A PubSub message service to send/receive messages. - [Remote Entity](documentation/remote-entity.md): A base entity to be used with migrations. - [Testing](documentation/testing.md): Various features to help with automated testing. +- [User expire](/documentation/user-expire.md): Block inactive accounts automatically. ## Contact diff --git a/documentation/user-expire.md b/documentation/user-expire.md new file mode 100644 index 00000000..4a87d13f --- /dev/null +++ b/documentation/user-expire.md @@ -0,0 +1,9 @@ +# User expire + +Accounts that have been inactive for longer than 6 months are blocked automatically. + +This feature can be disabled by changing `user_expire` setting to false: +```yaml +# conf/cmi/helfi_api_base.features.yml +user_expire: false +``` From 1a7d9816ef01b2bd9ac68842bd2729a546bb9868 Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 14:16:21 +0300 Subject: [PATCH 13/15] UHF-10012: Fixed typo --- README.md | 2 +- documentation/disable-user-password.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 76d0321d..3719fdfc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A base module for [drupal-helfi-platform](https://github.com/City-of-Helsinki/dr - [Debug collector](documentation/debug.md): A plugin to collect and show various debug information in one place. - [Default language resolver](documentation/default-languages.md): A service to handle default primary languages and language fallbacks. - [Deploy hooks](documentation/deploy-hooks.md): Allows custom tasks to be run before or after deployment. -- [Disable user password](/documentation/disable-user-password.md): A deployment hook to prevent users from logging in using password. +- [Disable user password](/documentation/disable-user-password.md): A deployment hook to prevent configured users from logging in using password. - [Disable email sending](/documentation/disable-email-sending.md): Sending email is disabled by default. - [Environment resolver](documentation/environment-resolver.md): A service to fetch metadata for given project. - [Feature toggle](/documentation/feature-toggle.md): Allow certain functionality to be toggled on/off. diff --git a/documentation/disable-user-password.md b/documentation/disable-user-password.md index 650a07df..8a3fbceb 100644 --- a/documentation/disable-user-password.md +++ b/documentation/disable-user-password.md @@ -1,6 +1,6 @@ # Disable user password -Provides a [Deployment hook](/documentation/deploy-hooks.md) that sets listed users' password to NULL. +Provides a [Deployment hook](/documentation/deploy-hooks.md) that sets configured users' password to NULL. ## Usage From 5407428b6d4cfa1a86b3a17650e636e7240e546e Mon Sep 17 00:00:00 2001 From: tuutti Date: Wed, 24 Apr 2024 14:24:37 +0300 Subject: [PATCH 14/15] UHF-10012: Fixed comment --- src/Plugin/rest/resource/DebugDataResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/rest/resource/DebugDataResource.php b/src/Plugin/rest/resource/DebugDataResource.php index e9af0ff5..f49c11ec 100644 --- a/src/Plugin/rest/resource/DebugDataResource.php +++ b/src/Plugin/rest/resource/DebugDataResource.php @@ -10,7 +10,7 @@ /** * Represents Debug records as resources. * - * @todo Remove this once we have removed it from everything else. + * @todo Remove this once the REST configuration is removed from all projects. * * @RestResource ( * id = "helfi_debug_data", From 253b2064e32cf1dc846d714b2c097f30d54364e8 Mon Sep 17 00:00:00 2001 From: tuutti Date: Thu, 25 Apr 2024 11:01:48 +0300 Subject: [PATCH 15/15] UHF-10012: Added query tag --- src/UserExpire/UserExpireManager.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/UserExpire/UserExpireManager.php b/src/UserExpire/UserExpireManager.php index 3a49205c..1dda7ff0 100644 --- a/src/UserExpire/UserExpireManager.php +++ b/src/UserExpire/UserExpireManager.php @@ -38,8 +38,9 @@ public function cancelExpiredUsers() : void { $storage = $this->entityTypeManager->getStorage('user'); foreach ($this->getExpiredUserIds() as $uid) { - $storage->load($uid) - ->block() + $account = $storage->load($uid); + + $account->block() ->save(); } } @@ -73,6 +74,7 @@ public function getExpiredUserIds() : array { $query ->condition($expireCondition) ->condition('status', 1) + ->addTag('expired_users') // Make sure we have an upper bound. ->range(0, 50);