Add CLI command for migrating users according to a given domain

refs #2153
This commit is contained in:
Eric Lippmann 2017-06-07 16:25:35 +02:00 committed by Alexander A. Klimov
parent 755b2108a8
commit 178d5f8283
2 changed files with 512 additions and 0 deletions

View File

@ -0,0 +1,119 @@
<?php
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Migrate\Clicommands;
use Icinga\Cli\Command;
use Icinga\Module\Migrate\Config\UserDomainMigration;
use Icinga\User;
use Icinga\Util\StringHelper;
class ConfigCommand extends Command
{
/**
* Rename users and user configurations according to a given domain
*
* The following configurations are taken into account:
* - Announcements
* - Preferences
* - Dashboards
* - Custom navigation items
* - Role configuration
* - Users and group memberships in database backends, if configured
*
* USAGE:
*
* icingacli migrate config users [options]
*
* OPTIONS:
*
* --to-domain=<to-domain> The new domain for the users
*
* --from-domain=<from-domain> Migrate only the users with the given domain.
* Use this switch in combination with --to-domain.
*
* --user=<user> Migrate only the given user in the format <user> or <user@domain>
*
* --map-file=<mapfile> File to use for renaming users
*
* --separator=<separator> Separator for the map file
*
* EXAMPLES:
*
* icingacli migrate config users ...
*
* Add the domain "icinga.com" to all users:
*
* --to-domain icinga.com
*
* Set the domain "example.com" on all users that have the domain "icinga.com":
*
* --to-domain example.com --from-domain icinga.com
*
* Set the domain "icinga.com" on the user "icingaadmin":
*
* --to-domain icinga.com --user icingaadmin
*
* Set the domain "icinga.com" on the users "icingaadmin@icinga.com"
*
* --to-domain example.com --user icingaadmin@icinga.com
*
* Rename users according to a map file:
*
* --map-file /path/to/mapfile --separator :
*
* MAPFILE:
*
* You may rename users according to a given map file. The map file must be separated by newlines. Each line then
* is specified in the format <from><separator><to>. The separator is specified with the --separator switch.
*
* Example content:
*
* icingaadmin:icingaadmin@icinga.com
* jdoe@example.com:jdoe@icinga.com
* rroe@icinga:rroe@icinga.com
*/
public function usersAction()
{
if ($this->params->has('map-file')) {
$mapFile = $this->params->get('map-file');
$separator = $this->params->getRequired('separator');
$source = trim(file_get_contents($mapFile));
$source = StringHelper::trimSplit($source, "\n");
$map = array();
array_walk($source, function ($item) use ($separator, &$map) {
list($from, $to) = StringHelper::trimSplit($item, $separator, 2);
$map[$from] = $to;
});
$migration = UserDomainMigration::fromMap($map);
} else {
$toDomain = $this->params->getRequired('to-domain');
$fromDomain = $this->params->get('from-domain');
$user = $this->params->get('user');
if ($user === null) {
$migration = UserDomainMigration::fromDomains($toDomain, $fromDomain);
} else {
if ($fromDomain !== null) {
$this->fail(
"Ambiguous arguments: Can't use --user in combination with --from-domain."
. " Please use the user@domain syntax for the --user switch instead."
);
}
$user = new User($user);
$migrated = clone $user;
$migrated->setDomain($toDomain);
$migration = UserDomainMigration::fromMap(array($user->getUsername() => $migrated->getUsername()));
}
}
$migration->migrate();
}
}

View File

@ -0,0 +1,393 @@
<?php
/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Migrate\Config;
use Icinga\Application\Config;
use Icinga\Data\Db\DbConnection;
use Icinga\Data\Filter\Filter;
use Icinga\Data\ResourceFactory;
use Icinga\User;
use Icinga\Util\DirectoryIterator;
use Icinga\Util\StringHelper;
use Icinga\Web\Announcement\AnnouncementIniRepository;
class UserDomainMigration
{
protected $toDomain;
protected $fromDomain;
protected $map;
public static function fromMap(array $map)
{
$static = new static();
$static->map = $map;
return $static;
}
public static function fromDomains($toDomain, $fromDomain = null)
{
$static = new static();
$static->toDomain = $toDomain;
$static->fromDomain = $fromDomain;
return $static;
}
protected function mustMigrate(User $user)
{
if ($user->getUsername() === '*') {
return false;
}
if ($this->map !== null) {
return isset($this->map[$user->getUsername()]);
}
if ($this->fromDomain !== null && $user->hasDomain() && $user->getDomain() !== $this->fromDomain) {
return false;
}
return true;
}
protected function migrateUser(User $user)
{
$migrated = clone $user;
if ($this->map !== null) {
$migrated->setUsername($this->map[$user->getUsername()]);
} else {
$migrated->setDomain($this->toDomain);
}
return $migrated;
}
protected function migrateAnnounces()
{
$announces = new AnnouncementIniRepository();
$query = $announces->select(array('author'));
if ($this->map !== null) {
$query->where('author', array_keys($this->map));
}
$migratedUsers = array();
foreach ($announces->select(array('author')) as $announce) {
$user = new User($announce->author);
if (! $this->mustMigrate($user)) {
continue;
}
if (isset($migratedUsers[$user->getUsername()])) {
continue;
}
$migrated = $this->migrateUser($user);
$announces->update(
'announcement',
array('author' => $migrated->getUsername()),
Filter::where('author', $user->getUsername())
);
$migratedUsers[$user->getUsername()] = true;
}
}
protected function migrateDashboards()
{
$directory = Config::resolvePath('dashboards');
$migration = array();
if (DirectoryIterator::isReadable($directory)) {
foreach (new DirectoryIterator($directory) as $username => $path) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$migration[$path] = dirname($path) . '/' . $migrated->getUsername();
}
foreach ($migration as $from => $to) {
rename($from, $to);
}
}
}
protected function migrateNavigation()
{
$directory = Config::resolvePath('navigation');
foreach (new DirectoryIterator($directory, 'ini') as $file) {
$config = Config::fromIni($file);
foreach ($config as $navigation) {
$owner = $navigation->owner;
if (! empty($owner)) {
$user = new User($owner);
if ($this->mustMigrate($user)) {
$migrated = $this->migrateUser($user);
$navigation->owner = $migrated->getUsername();
}
}
$users = $navigation->users;
if (! empty($users)) {
$users = StringHelper::trimSplit($users);
foreach ($users as &$username) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$username = $migrated->getUsername();
}
$navigation->users = implode(',', $users);
}
}
$config->saveIni();
}
}
protected function migratePreferences()
{
$config = Config::app();
$type = $config->get('global', 'config_backend', 'ini');
switch ($type) {
case 'ini':
$directory = Config::resolvePath('preferences');
$migration = array();
if (DirectoryIterator::isReadable($directory)) {
foreach (new DirectoryIterator($directory) as $username => $path) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$migration[$path] = dirname($path) . '/' . $migrated->getUsername();
}
foreach ($migration as $from => $to) {
rename($from, $to);
}
}
break;
case 'db':
/** @var DbConnection $conn */
$conn = ResourceFactory::create($config->get('global', 'config_resource'));
$query = $conn
->select()
->from('icingaweb_user_preference', array('username'))
->group('username');
if ($this->map !== null) {
$query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
}
$users = $query->fetchColumn();
$migration = array();
foreach ($users as $username) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$migration[$username] = $migrated->getUsername();
}
if (! empty($migration)) {
$conn->getDbAdapter()->beginTransaction();
foreach ($migration as $originalUsername => $username) {
$conn->update(
'icingaweb_user_preference',
array('username' => $username),
Filter::where('username', $originalUsername)
);
}
$conn->getDbAdapter()->commit();
}
}
}
protected function migrateRoles()
{
$roles = Config::app('roles');
foreach ($roles as $role) {
$users = $role->users;
if (empty($users)) {
continue;
}
$users = StringHelper::trimSplit($users);
foreach ($users as &$username) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$username = $migrated->getUsername();
}
$role->users = implode(',', $users);
}
$roles->saveIni();
}
protected function migrateUsers()
{
foreach (Config::app('authentication') as $name => $config) {
if (strtolower($config->backend) !== 'db') {
continue;
}
/** @var DbConnection $conn */
$conn = ResourceFactory::create($config->resource);
$query = $conn
->select()
->from('icingaweb_user', array('name'))
->group('name');
if ($this->map !== null) {
$query->applyFilter(Filter::matchAny(Filter::where('name', array_keys($this->map))));
}
$users = $query->fetchColumn();
$migration = array();
foreach ($users as $username) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$migration[$username] = $migrated->getUsername();
}
if (! empty($migration)) {
$conn->getDbAdapter()->beginTransaction();
foreach ($migration as $originalUsername => $username) {
$conn->update(
'icingaweb_user',
array('name' => $username),
Filter::where('name', $originalUsername)
);
}
$conn->getDbAdapter()->commit();
}
}
foreach (Config::app('groups') as $name => $config) {
if (strtolower($config->backend) !== 'db') {
continue;
}
/** @var DbConnection $conn */
$conn = ResourceFactory::create($config->resource);
$query = $conn
->select()
->from('icingaweb_group_membership', array('username'))
->group('username');
if ($this->map !== null) {
$query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
}
$users = $query->fetchColumn();
$migration = array();
foreach ($users as $username) {
$user = new User($username);
if (! $this->mustMigrate($user)) {
continue;
}
$migrated = $this->migrateUser($user);
$migration[$username] = $migrated->getUsername();
}
if (! empty($migration)) {
$conn->getDbAdapter()->beginTransaction();
foreach ($migration as $originalUsername => $username) {
$conn->update(
'icingaweb_group_membership',
array('username' => $username),
Filter::where('username', $originalUsername)
);
}
$conn->getDbAdapter()->commit();
}
}
}
public function migrate()
{
$this->migrateAnnounces();
$this->migrateDashboards();
$this->migrateNavigation();
$this->migratePreferences();
$this->migrateRoles();
$this->migrateUsers();
}
}