Skip to content

Commit

Permalink
Merge pull request #305 from City-of-Helsinki/UHF-9832
Browse files Browse the repository at this point in the history
UHF-9832: Added user sanitation service, drush command and form.
  • Loading branch information
khalima authored May 7, 2024
2 parents 975ebc4 + 3aa3f76 commit 8b86078
Show file tree
Hide file tree
Showing 12 changed files with 866 additions and 0 deletions.
14 changes: 14 additions & 0 deletions public/modules/custom/infofinland_user_cancel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# User sanitation

Administrators have the capability to sanitize users from both list operations in the `People` page and from the user edit page, provided the user has been blocked.

## Usage

To utilize this feature, assign the `sanitize user accounts` permission to the administrator role or its equivalent. There is also a drush command to run the same feature.

### Drush command
Sanitize username and email fields for uids 5,6 and 7.
`drush user:sanitize 5,6,7 --fields=username,email`

Sanitize username, email and password for uid 5
`drush user:sanitize 5`
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
infofinland_user_cancel.user_sanitize_commands:
class: \Drupal\infofinland_user_cancel\Commands\UserSanitizeCommands
arguments: ['@infofinland_user_cancel.user_entity_sanitizer']
tags:
- { name: drush.command }
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* and we can reassign module weights / run order if necessary.
*/

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\user\UserInterface;

/**
Expand Down Expand Up @@ -79,5 +82,69 @@ function _infofinland_user_cancel_reassign_nodes_to_anonymous(UserInterface $acc
]
));
}
}

/**
* Implements hook_entity_operation().
*/
function infofinland_user_cancel_entity_operation(EntityInterface $entity): array {
// Add sanitize user account -operation to user action list.
$operations = [];

/** @var \Drupal\user\UserInterface $current_user */
$current_user = \Drupal::currentUser();
if (
$entity instanceof UserInterface &&
$current_user->hasPermission('sanitize user accounts') &&
$entity->isBlocked()
) {
$operations['sanitize_user_entity'] = [
'title' => t('Sanitize user account'),
'url' => Url::fromRoute('infofinland_user_cancel.user_sanitize', ['user' => $entity->id()]),
'weight' => 50,
];
}
return $operations;
}

/**
* Implements hook_form_FORM_ID_alter().
*/
function infofinland_user_cancel_form_user_form_alter(array &$form): void {
/** @var \Drupal\user\UserInterface $current_user */
$current_user = \Drupal::currentUser();

/** @var \Drupal\user\UserInterface $entity */
$entity = \Drupal::routeMatch()->getParameter('user');

// Add sanitize user account -operation to user actions if the current user
// has the 'sanitize user accounts' permission and the user is blocked.
if (
$entity instanceof UserInterface &&
$current_user->hasPermission('sanitize user accounts') &&
$entity->isBlocked()
) {
$destination = Url::fromRoute(
'entity.user.canonical',
['user' => $entity->id()]
)->toString();

$form['actions']['sanitize'] = [
'#attributes' => [
'class' => [
'button',
'button--danger',
],
],
'#button_type' => 'danger',
'#title' => t('Sanitize account'),
'#type' => 'link',
'#url' => Url::fromRoute(
'infofinland_user_cancel.user_sanitize',
['user' => $entity->id()],
['query' => ['destination' => $destination]],
),
'#weight' => 11,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sanitize user accounts:
title: Sanitize user accounts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
infofinland_user_cancel.user_sanitize:
path: '/user/{user}/sanitize'
defaults:
_form: 'Drupal\infofinland_user_cancel\Entity\Form\UserEntitySanitizeForm'
_title: 'Sanitize user account'
requirements:
_permission: 'sanitize user accounts'
_csrf_token: 'TRUE'
options:
_admin_route: TRUE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
services:
Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer: '@infofinland_user_cancel.user_entity_sanitizer'
infofinland_user_cancel.user_entity_sanitizer:
class: Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer
autowire: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Drupal\infofinland_user_cancel\Commands;

use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer;
use Drush\Attributes\Argument;
use Drush\Attributes\Command;
use Drush\Attributes\Option;
use Drush\Attributes\Usage;
use Drush\Commands\DrushCommands;
use Drush\Utils\StringUtils;

/**
* A Drush command file.
*/
final class UserSanitizeCommands extends DrushCommands {

use StringTranslationTrait;

const FIELDS = ['username', 'email', 'password'];

/**
* Constructs a new instance.
*
* @param \Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer $sanitizer
* UserEntitySanitizer service.
*/
public function __construct(
protected UserEntitySanitizer $sanitizer,
) {
}

/**
* Sanitizes user entity fields.
*
* @param string $uids
* The user IDs.
* @param array $options
* The options.
*
* @return int
* The exit code.
*/
#[Command(name: 'user:sanitize')]
#[Argument(name: 'uids', description: 'A comma delimited list of user ids.')]
#[Option(name: 'fields', description: 'A comma delimited list of fields to sanitize.')]
#[Usage(name: 'drush user:sanitize 5,6,7 --fields=username,email', description: 'Sanitize username and email fields for uids 5,6 and 7.')]
#[Usage(name: 'drush user:sanitize 5', description: 'Sanitize username, email and password for uid 5')]
public function sanitize(string $uids, array $options = ['fields' => self::FIELDS]) : int {
$sanitized_users = [];

// If fields are set, use only the specified ones.
if ($fields = StringUtils::csvToArray($options['fields'])) {
$fields = array_intersect($fields, self::FIELDS);
if (!$fields) {
$this->logger->notice(dt('There was an error in the fields list. Check the "fields" option values (username, email, password).'));
return DrushCommands::EXIT_FAILURE;
}
}

// If no fields are set, use all fields.
if (!$fields) {
$fields = self::FIELDS;
}

// Use sanitizer service to Sanitize the user entity fields.
foreach (StringUtils::csvToArray($uids) as $uid) {
try {
$this->sanitizer->sanitizeUserEntity((int) $uid, $fields);
$sanitized_users[] = $uid;
}
catch (\Exception $e) {
$this->logger->error($e->getMessage());
continue;
}
}

if (!$sanitized_users) {
$this->logger->notice(dt('No users were sanitized.'));
return DrushCommands::EXIT_SUCCESS;
}

$this->logger->notice(dt('!fields fields were sanitized for UIDs !uids.', [
'!fields' => implode(', ', $fields),
'!uids' => implode(', ', $sanitized_users),
]));
return DrushCommands::EXIT_SUCCESS;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace Drupal\infofinland_user_cancel\Entity\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides the form for sanitizing chosen user entity.
*/
final class UserEntitySanitizeForm extends FormBase {

/**
* UserEntitySanitizeForm constructor.
*
* @param \Drupal\infofinland_user_cancel\Entity\Utility\UserEntitySanitizer $sanitizer
* UserEntitySanitizer service.
*/
public function __construct(
protected UserEntitySanitizer $sanitizer,
) {
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new self(
$container->get('infofinland_user_cancel.user_entity_sanitizer')
);
}

/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'user_entity_sanitize_form';
}

/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, AccountInterface $user = NULL): array {

if (!$user instanceof UserInterface) {
return ['#markup' => $this->t('User account not found.')];
}

if ($user->isActive()) {
return ['#markup' => $this->t('User account is not deactivated.')];
}

$form_state->set('account', $user);
$form['#title'] = $this->t('Sanitize user account');

$form['title'] = [
'#markup' => "<h2>{$this->t('Select the fields to be sanitized for the username %user_name', ['%user_name' => $user->getAccountName()])}</h2>",
];

$form['fields'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Fields'),
'#options' => [
'email' => $this->t('Email address'),
'username' => $this->t('Username'),
'password' => $this->t('Password'),
],
];

$form['confirm'] = [
'#type' => 'radio',
'#title' => $this->t('I understand that this action will sanitize all selected data from the user account and the action cannot be undone.'),
'#required' => TRUE,
];

$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Sanitize'),
];
return $form;
}

/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if (!$form_state->get('account')->id()) {
$this->messenger()->addError($this->t('There was an error with the account.'));
return;
}
$user = $form_state->get('account');
$values = $form_state->getValues()['fields'];
$fields = [];

// Add only selected fields.
foreach ($values as $key => $value) {
if ($value) {
$fields[] = $key;
}
}

// Use sanitizer service to Sanitize the user entity fields.
try {
$operation = $this->sanitizer->sanitizeUserEntity($user, $fields);

// If the operation is 0, none of the field values were saved, probably
// due to a non-existent field selections in the form.
if ($operation === 0) {
$this->messenger()->addError($this->t('There was an error with saving the sanitized information to the account.'));
return;
}
}
catch (\Exception $e) {
$this->messenger()->addError($e->getMessage());
}

// Return to People page after successful sanitization.
$form_state->setRedirect('entity.user.collection');

// Add a status message with the uid of the sanitized user.
$this->messenger()->addStatus($this->t('User account id %user_id was sanitized.', [
'%user_id' => $user->id(),
]));
}

}
Loading

0 comments on commit 8b86078

Please sign in to comment.