UNPKG

@okta/stormpath-migration

Version:

Migration tool to import Stormpath data into an Okta tenant

140 lines (127 loc) 6.01 kB
/*! * Copyright (c) 2017, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") * * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ const logger = require('../util/logger'); const UnifiedAccounts = require('../stormpath/unified-accounts'); const GroupMembershipMap = require('../stormpath/group-membership-map'); const config = require('../util/config'); const stormpathExport = require('../stormpath/stormpath-export'); const cache = require('../migrators/util/cache'); const { batch } = require('../util/concurrency'); async function getDirectoryProviders() { const directories = await stormpathExport.getDirectories(); logger.info(`Mapping ${directories.length} directories to providerIds`); return directories.mapToObject((directory, map) => { map[directory.id] = directory.provider.providerId; }); } /** * Introspect the stormpath export and set up initial mappings: * 1. Custom schema definitions * 2. Unified accounts */ async function introspect() { logger.header('Introspecting stormpath export'); const directoryProviders = await getDirectoryProviders(); const accountLinks = await stormpathExport.getAccountLinks(); const unifiedAccounts = new UnifiedAccounts(accountLinks); let numADLDAPAccounts = 0; let numUnverified = 0; const skipAccounts = await unifiedAccounts.restore(); const accounts = await stormpathExport.getAccounts(skipAccounts); const totalAccounts = accounts.length; let nextProgressPoint = config.checkpointProgressLimit; logger.info(`Pre-processing ${accounts.length} stormpath accounts`); await accounts.batch( async (account) => { try { const providerId = directoryProviders[account.directory.id]; if (!providerId) { unifiedAccounts.discardAccount(account); return logger.error(`Missing directory id=${account.directory.id}. Skipping account id=${account.id}.`); } if (providerId === 'ad' || providerId === 'ldap') { numADLDAPAccounts++; unifiedAccounts.discardAccount(account); return logger.verbose(`Skipping account id=${account.id}. Import using the Okta ${providerId.toUpperCase()} agent.`); } if (account.status === 'UNVERIFIED') { numUnverified++; unifiedAccounts.discardAccount(account); return logger.verbose(`Skipping unverified account id=${account.id}`); } await unifiedAccounts.addAccount(account); } catch (err) { logger.error(err); } }, async (numProcessed) => { if (numProcessed >= nextProgressPoint || numProcessed === totalAccounts) { const percent = Math.round(numProcessed / totalAccounts * 100); logger.info(`-- Processed ${numProcessed} accounts (${percent}%) --`); nextProgressPoint += config.checkpointProgressLimit; } await unifiedAccounts.save(); } ); if (numADLDAPAccounts > 0) { logger.warn(`Skipped ${numADLDAPAccounts} AD or LDAP accounts. Import using the Okta AD or LDAP agent.`); } if (numUnverified > 0) { logger.warn(`Skipped ${numUnverified} unverified accounts.`); } const problemAccounts = unifiedAccounts.getProblemUsernameAccounts(); if (problemAccounts.length > 0) { const lg = logger.group('Found stormpath usernames that conflict with other account usernames', 'error'); for (let account of problemAccounts) { const username = account.username; const original = username.replace('@emailnotprovided.local', ''); const sg = logger.group(`id=${account.id} orig_username=${original} new_username=${username}`, 'error'); for (let conflict of account.conflicts) { logger.error(`Conflicts with id=${conflict.id} username=${conflict.username}`); } sg.end(); } logger.error('Fix this by updating these usernames to emails, or contacting Okta support.'); lg.end(); } cache.unifiedAccounts = unifiedAccounts; const schema = unifiedAccounts.getSchema(); cache.customSchemaProperties = schema.properties; cache.customSchemaTypeMap = schema.schemaTypeMap; logger.info(`Found ${Object.keys(cache.customSchemaProperties).length} custom schema properties`); const groupMembershipMap = new GroupMembershipMap(); logger.info('Loading Stormpath groupMembershipMap from checkpoint file'); await groupMembershipMap.restore(); if (groupMembershipMap.processed()) { logger.info('Successfully loaded groupMembershipMap'); } else { logger.info('No existing checkpoint file for groupMembershipMap'); const groupMemberships = await stormpathExport.getGroupMemberships(); const totalGroupMemberships = groupMemberships.length; let nextMembershipCheckpoint = config.checkpointProgressLimit; logger.info(`Pre-processing ${totalGroupMemberships} Stormpath group memberships`); await groupMemberships.batch( (membership) => groupMembershipMap.add(membership), (numProcessed) => { if (numProcessed >= nextMembershipCheckpoint || numProcessed === totalGroupMemberships) { const percent = Math.round(numProcessed / totalGroupMemberships * 100); logger.info(`-- Processed ${numProcessed} group memberships (${percent}%) --`); nextMembershipCheckpoint += config.checkpointProgressLimit; } } ); logger.info('Saving groupMembershipMap to checkpoint file'); await groupMembershipMap.save(); } cache.groupMembershipMap = groupMembershipMap.getMembershipMap(); } module.exports = introspect;