-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
SanitizeUserTableCommands.php
132 lines (118 loc) · 5.97 KB
/
SanitizeUserTableCommands.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php
declare(strict_types=1);
namespace Drush\Drupal\Commands\sql;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Password\PasswordInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Drush\Sql\SqlBase;
use Drush\Utils\StringUtils;
use Symfony\Component\Console\Input\InputInterface;
/**
* A sql-sanitize plugin.
*/
final class SanitizeUserTableCommands extends DrushCommands implements SanitizePluginInterface
{
public function __construct(
protected \Drupal\Core\Database\Connection $database,
protected PasswordInterface $passwordHasher,
protected EntityTypeManagerInterface $entityTypeManager
) {
}
/**
* Sanitize emails and passwords. This also an example of how to write a
* database sanitizer for sql-sync.
*/
#[CLI\Hook(type: HookManager::POST_COMMAND_HOOK, target: SanitizeCommands::SANITIZE)]
public function sanitize($result, CommandData $commandData): void
{
$options = $commandData->options();
$query = $this->database->update('users_field_data')->condition('uid', 0, '>');
$messages = [];
// Sanitize passwords.
if ($this->isEnabled($options['sanitize-password'])) {
$password = $options['sanitize-password'];
if (is_null($password)) {
$password = StringUtils::generatePassword();
}
// Mimic Drupal's /scripts/password-hash.sh
$hash = $this->passwordHasher->hash($password);
$query->fields(['pass' => $hash]);
$messages[] = dt('User passwords sanitized.');
}
// Sanitize email addresses.
if ($this->isEnabled($options['sanitize-email'])) {
if (str_contains($options['sanitize-email'], '%')) {
// We need a different sanitization query for MSSQL, Postgres and Mysql.
$sql = SqlBase::create($commandData->input()->getOptions());
$db_driver = $sql->scheme();
if ($db_driver == 'pgsql') {
$email_map = ['%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '"];
$new_mail = "'" . str_replace(array_keys($email_map), array_values($email_map), $options['sanitize-email']) . "'";
} elseif ($db_driver == 'mssql') {
$email_map = ['%uid' => "' + uid + '", '%mail' => "' + replace(mail, '@', '_') + '", '%name' => "' + replace(name, ' ', '_') + '"];
$new_mail = "'" . str_replace(array_keys($email_map), array_values($email_map), $options['sanitize-email']) . "'";
} else {
$email_map = ['%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '"];
$new_mail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $options['sanitize-email']) . "')";
}
$query->expression('mail', $new_mail);
$query->expression('init', $new_mail);
} else {
$query->fields(['mail' => $options['sanitize-email']]);
}
$messages[] = dt('User emails sanitized.');
}
if (!empty($options['ignored-roles'])) {
$roles = explode(',', $options['ignored-roles']);
/** @var \Drupal\Core\Database\Query\SelectInterface $roles_query */
$roles_query = $this->database->select('user__roles', 'ur');
$roles_query
->condition('roles_target_id', $roles, 'IN')
->fields('ur', ['entity_id']);
$roles_query_results = $roles_query->execute();
$ignored_users = $roles_query_results->fetchCol();
if (!empty($ignored_users)) {
$query->condition('uid', $ignored_users, 'NOT IN');
$messages[] = dt('User emails and passwords for the specified roles preserved.');
}
}
if ($messages) {
$query->execute();
$this->entityTypeManager->getStorage('user')->resetCache();
foreach ($messages as $message) {
$this->logger()->success($message);
}
}
}
#[CLI\Hook(type: HookManager::OPTION_HOOK, target: SanitizeCommands::SANITIZE)]
#[CLI\Option(name: 'sanitize-email', description: 'The pattern for test email addresses in the sanitization operation, or <info>no</info> to keep email addresses unchanged. May contain replacement patterns <info>%uid</info>, <info>%mail</info> or <info>%name</info>.')]
#[CLI\Option(name: 'sanitize-password', description: 'By default, passwords are randomized. Specify <info>no</info> to disable that. Specify any other value to set all passwords to that value.')]
#[CLI\Option(name: 'ignored-roles', description: 'A comma delimited list of roles. Users with at least one of the roles will be exempt from sanitization.')]
public function options($options = ['sanitize-email' => 'user+%uid@localhost.localdomain', 'sanitize-password' => null, 'ignored-roles' => null]): void
{
}
#[CLI\Hook(type: HookManager::ON_EVENT, target: SanitizeCommands::CONFIRMS)]
public function messages(&$messages, InputInterface $input): void
{
$options = $input->getOptions();
if ($this->isEnabled($options['sanitize-password'])) {
$messages[] = dt('Sanitize user passwords.');
}
if ($this->isEnabled($options['sanitize-email'])) {
$messages[] = dt('Sanitize user emails.');
}
if (in_array('ignored-roles', $options)) {
$messages[] = dt('Preserve user emails and passwords for the specified roles.');
}
}
/**
* Test an option value to see if it is disabled.
*/
protected function isEnabled(?string $value): bool
{
return $value != 'no' && $value != '0';
}
}