Add CLI command for migrating users according to a given domain
refs #2153
This commit is contained in:
parent
755b2108a8
commit
178d5f8283
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue