A production-ready Python toolkit for migrating identity data from a source identity provider into OneLogin:
- Multi-provider: Pluggable source provider architecture — Okta supported out of the box, straightforward to extend
- Multi-interface: CLI for automation, GUI wizard for interactive migrations
- Safe by default: Dry-run mode, field validation, resumable migrations with state persistence
- Concurrent processing: Thread-safe with automatic rate limiting and backoff
- Flexible data export: JSON snapshots or OneLogin bulk CSV format
- Smart field mapping: Automatic custom attribute creation and normalization
- Quick Start
- Commands
- Configuration
- Secure Credential Management
- GUI Features
- Field Mapping
- Performance
- Troubleshooting
- Development
This project uses a monorepo structure with three packages:
- core - Core migration library
- cli - Command-line interface
- gui - Graphical user interface
-
Create and activate a virtual environment (macOS/Linux):
python3 -m venv .venv source .venv/bin/activate -
Install packages using the development setup script:
# Install all packages (recommended for development) ./scripts/dev-install.sh # Or install individually: pip install -e packages/core pip install -e packages/cli pip install -e packages/gui
-
Verify installation:
python -m onelogin_migration_cli.app --help
Note: Credentials are stored securely in your system keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service) instead of configuration files. See Secure Credential Management for details.
# Set PYTHONPATH for package discovery
export PYTHONPATH=packages/cli/src:packages/core/src
# 1. Store credentials securely (done once)
python -m onelogin_migration_cli.app credentials set source token
python -m onelogin_migration_cli.app credentials set onelogin client_secret
# 2. Test export from source provider (safe, read-only)
python -m onelogin_migration_cli.app plan --config config/migration.yaml
# 3. Preview custom attributes to create
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yaml --dry-run
# 4. Pre-create custom attributes in OneLogin
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yaml
# 5. Run migration (dry-run mode by default)
python -m onelogin_migration_cli.app migrate --config config/migration.yaml
# Or use the GUI wizard
export PYTHONPATH=packages/gui/src:packages/core/src
python -m onelogin_migration_gui.mainAll commands use the CLI module. Set PYTHONPATH first:
export PYTHONPATH=packages/cli/src:packages/core/srcExport data from your source provider without making any changes to OneLogin. Perfect for auditing and planning.
python -m onelogin_migration_cli.app plan --config config/migration.yaml -vWhat it does:
- Exports users, groups, memberships, and applications from your source provider
- Saves
source_export.jsonin the configured export directory - Creates timestamped snapshots:
{source}_{category}_{timestamp}.json - No writes to OneLogin (safe for exploration)
Options:
-v, --verbose- Enable debug logging--output PATH- Custom output path for export
Execute the complete migration workflow with safety controls.
python -m onelogin_migration_cli.app migrate --config config/migration.yaml -vWhat it does:
- Exports all data from source provider (if not provided)
- Cleans up existing OneLogin roles (except defaults)
- Creates roles (groups), users, and applications in OneLogin
- Assigns users to roles and roles to applications
Options:
--dry-run- Simulate migration without writes (overrides config)--bulk-user-upload- Generate OneLogin CSV instead of API calls--export PATH- Use pre-generated source export JSON-v, --verbose- Enable debug logging
Safety features:
- Honors
dry_runsetting from config - State persistence for resume capability
- Automatic rate limiting and retry logic
- Account owner protection
Launch a graphical wizard with step-by-step guidance and real-time progress monitoring.
python -m onelogin_migration_gui.main --config config/migration.yamlFeatures:
- Connection testing for source provider and OneLogin
- Live settings editor with validation
- Category selection (users, groups, apps, policies)
- Real-time progress bars and log streaming
- Bulk CSV export option
Display the current configuration with secrets masked for troubleshooting.
python -m onelogin_migration_cli.app show-config --config config/migration.yamlAnalyze source provider user profiles and automatically create all necessary custom attributes in OneLogin before migration.
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yamlWhat it does:
- Exports users from source provider (or uses existing export)
- Analyzes all user profile fields across all users
- Identifies which fields become custom attributes
- Creates those custom attributes in OneLogin with proper naming
Use cases:
- Preview custom attributes before migration
- Pre-provision attributes for bulk CSV import
- Troubleshoot attribute mapping issues
- Verify attribute normalization (camelCase → snake_case)
Options:
--export PATH- Use pre-generated source export instead of fetching--dry-run- Preview attributes without creating them-v, --verbose- Show detailed attribute analysis
Example workflow:
# Preview what attributes would be created
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yaml --dry-run
# Create the attributes in OneLogin
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yaml
# Use with existing export
python -m onelogin_migration_cli.app plan --config config/migration.yaml --output source_export.json
python -m onelogin_migration_cli.app provision-attributes --config config/migration.yaml --export source_export.jsonOutput example:
Analyzing 1,247 users for custom attributes...
Discovered 15 custom attributes:
1. city
2. country_code
3. display_name
4. employee_number
5. manager_email
6. second_email
7. state
8. street_address
9. zip_code
10. cost_center
...
Provisioning 15 attributes in OneLogin...
✓ Created 12 new attributes
ℹ 3 attributes already exist
Create config/migration.yaml from the template:
cp config/migration.template.yaml config/migration.yaml# Migration behavior
dry_run: true # Safe mode - no writes to OneLogin
chunk_size: 200 # Items per batch
export_directory: artifacts # Where to save exports
concurrency_enabled: false # Enable multi-threading
max_workers: 4 # Worker threads (auto-calculated if omitted)
bulk_user_upload: false # Generate CSV instead of API calls
pass_app_parameters: true # Extract and pass app parameters during migration
# Source provider configuration (Okta)
okta:
domain: your-org.okta.com # Your Okta domain
token: YOUR_OKTA_API_TOKEN # Admin API token
rate_limit_per_minute: 600 # API rate limit
page_size: 200 # Items per page
# OneLogin target configuration
onelogin:
client_id: YOUR_ONELOGIN_CLIENT_ID # OAuth client ID
client_secret: YOUR_ONELOGIN_CLIENT_SECRET # OAuth client secret
region: us # us or eu
subdomain: your-company # Tenant subdomain
rate_limit_per_hour: 5000 # API rate limit
# Optional: Migration categories
categories:
users: true
groups: true
applications: true
policies: false
# Optional: Application connector mappings
metadata:
application_connectors:
"slack":
"saml": 123456
"github":
"openid": 789012Source provider (Okta):
domain- Your source domain (e.g.,company.okta.com)token- Admin API token
OneLogin:
client_id/client_secret- OAuth credentials from OneLogin Admin → Developers → API Credentialsregion-usoreu(determines token endpoint:https://api.{region}.onelogin.com)subdomain- Tenant subdomain (e.g.,companyforcompany.onelogin.com)
Performance:
max_workers- If omitted, auto-calculated based on rate limits (recommended)concurrency_enabled- Enable thread-based parallelism
Output:
export_directory- Where to save JSON exports and CSVs (default:artifacts/)bulk_user_upload- Generate OneLogin CSV format instead of API calls
Application Migration:
pass_app_parameters- Extract and pass app configuration parameters to OneLogin (default:true)- SAML apps: Extracts ACS URL, Entity ID, NameID format, attribute statements, signature algorithm
- OIDC/OAuth apps: Extracts redirect URIs, grant types, response types, scopes, application type
- Generic metadata: URLs, icons, help links, user attribute mappings
- Only passes parameters when the OneLogin connector supports custom parameters (
allows_new_parametersflag) - Automatically filters out source-specific provider fields that won't work on OneLogin
- Speeds up migration by pre-populating app configuration
To see which OneLogin connectors support custom parameters, query the connector database:
from onelogin_migration_core.db import get_default_connector_db
db = get_default_connector_db()
# Get statistics
stats = db.get_connectors_stats()
print(f"Total connectors: {stats['total']}")
print(f"Support custom params: {stats['with_custom_params']}")
# List connectors that support custom parameters
for connector in db.get_connectors_with_custom_parameters():
print(f" - {connector['name']} (ID: {connector['id']})")
# Check a specific connector
connector = db.get_onelogin_connector(123456)
if connector and connector['allows_new_parameters']:
print(f"{connector['name']} supports custom parameters!")Note: The connector database is automatically refreshed from OneLogin API every 24 hours.
Safety:
dry_run- Simulates migration without writing to OneLogin (default:true)
Credentials are never stored in files:
- Source provider API tokens stored in system keyring (encrypted by OS)
- OneLogin client secrets stored in system keyring (encrypted by OS)
- Non-sensitive settings stored in JSON (
~/.onelogin-migration/settings.json)
Bundled App (Recommended):
- Download and launch the macOS/Windows app
- Enter credentials in the GUI
- Credentials automatically save to system keyring
- On next launch, credentials auto-fill from keyring
- Zero configuration files needed!
CLI Usage:
# Store credentials securely
python -m onelogin_migration_cli.app credentials set source token
python -m onelogin_migration_cli.app credentials set onelogin client_secret
# Verify stored credentials
python -m onelogin_migration_cli.app credentials list
# Test authentication
python -m onelogin_migration_cli.app credentials test source
python -m onelogin_migration_cli.app credentials test oneloginGUI with Auto-Save:
- Enter credentials in provider settings pages
- Click "Test Connection" to validate
- Credentials automatically save to keyring on successful validation
- Auto-prefill on next launch
| Feature | YAML files | Secure Storage |
|---|---|---|
| Storage | Plaintext files | OS-encrypted keyring |
| Backup risk | Credentials in backups | Only public settings backed up |
| Accidental commits | Easy to commit secrets | Impossible to leak credentials |
| Access control | File permissions only | OS authentication required |
| Memory protection | Plain strings | Auto-zeroing SecureString |
If you have existing YAML configuration files with credentials:
# Automatically extract and secure credentials
python -m onelogin_migration_cli.app credentials migrate config/migration.yaml
# This will:
# 1. Extract credentials to keyring
# 2. Create sanitized YAML backup
# 3. Overwrite original with safe version# Store credentials
python -m onelogin_migration_cli.app credentials set <service> <key> [--value VALUE]
# Retrieve credentials
python -m onelogin_migration_cli.app credentials get <service> <key> [--reveal]
# List all credentials
python -m onelogin_migration_cli.app credentials list
# Delete credentials
python -m onelogin_migration_cli.app credentials delete <service> <key> [--force]
# Test authentication
python -m onelogin_migration_cli.app credentials test source|onelogin
# Migrate from YAML
python -m onelogin_migration_cli.app credentials migrate <config_path>
# View audit log
python -m onelogin_migration_cli.app credentials audit [--limit N]System Keyring Locations:
- macOS: Keychain Access.app (
loginkeychain) - Windows: Windows Credential Manager
- Linux: Secret Service (GNOME Keyring, KWallet, etc.)
Non-Sensitive Settings:
- Location:
~/.onelogin-migration/settings.json - Contains: Domain names, rate limits, chunk sizes, etc.
- Safe to: Backup, share with team (no secrets!)
Audit Logs:
- Location:
~/.onelogin-migration/audit.log - Contains: Credential access events (no actual credential values)
Audit Logging:
python -m onelogin_migration_cli.app credentials audit --limit 10Backend Options:
python -m onelogin_migration_cli.app credentials set source token --backend keyring # Default
python -m onelogin_migration_cli.app credentials set source token --backend vault # AdvancedExport/Import (Vault Only):
python -m onelogin_migration_cli.app credentials export backup/vault.enc
python -m onelogin_migration_cli.app credentials import backup/vault.enc- Use system keyring (default) — OS-managed encryption
- Rotate API tokens regularly using
credentials set - Use
credentials testbefore migrations - Enable audit logging for tracking
- Delete old YAML files after migrating to secure storage
The graphical wizard provides a user-friendly interface for migrations with real-time feedback.
# Install GUI package
pip install -e packages/gui
# Launch GUI
export PYTHONPATH=packages/gui/src:packages/core/src
python -m onelogin_migration_gui.main- Welcome - Introduction and migration overview
- Source - Configure source provider credentials with connection test
- Target (OneLogin) - Configure OneLogin credentials with connection test
- Options - Set migration behavior (dry-run, concurrency, export format)
- Objects - Select categories to migrate (users, groups, apps, policies)
- Analysis - Analyze your source environment with detailed reports
- View user counts, attributes, and custom fields
- Review group memberships and hierarchies
- Examine application configurations and mappings
- Get OneLogin connector matching recommendations
- Export analysis results to CSV or XLSX
- Summary - Review all settings before starting
- Progress - Real-time progress bars and log streaming
Safety Controls:
- Nothing runs until you click Start Migration on the Summary step
- Dry-run mode enabled by default
- Connection tests must pass before proceeding
Progress Monitoring:
- Per-category progress bars (users, groups, applications)
- Live log streaming with color-coded messages
- Export file links for quick access
Flexible Options:
- Toggle verbose logging
- Enable/disable multi-threading
- Choose API calls vs. bulk CSV export
- Select specific migration categories
The toolkit automatically maps source provider profile fields to OneLogin's user schema. For Okta sources:
Core Identity Fields:
firstName→firstnamelastName→lastnameemail→emaillogin→username
Contact Information:
primaryPhone/phone/workPhone→phonemobilePhone→mobile_phonesecondEmail→second_email(custom attribute)
Organization Data:
company/organization→companydepartment→departmenttitle→title
Active Directory:
samAccountName→samaccountnameuserPrincipalName→userprincipalname
Status:
- Okta
ACTIVE→ OneLoginstate: 1 - Okta inactive → OneLogin
state: 0
Automatic Normalization: The toolkit automatically converts source provider profile fields to OneLogin custom attributes:
- camelCase → snake_case (e.g.,
employeeNumber→employee_number) - Field names truncated to 64 characters
- Invalid characters replaced with underscores
- Leading digits prefixed with underscore
Auto-Provisioning: Custom attributes are automatically created in OneLogin before user import (requires API permissions).
Excluded Fields: Complex data types (arrays, objects) are skipped to prevent validation errors.
Applications require OneLogin connector IDs. Configure mappings in metadata.application_connectors:
metadata:
application_connectors:
"slack":
"saml": 123456 # OneLogin Slack connector ID for SAML
"github":
"openid": 789012 # OneLogin GitHub connector for OIDCTo find connector IDs:
- Create the app manually in OneLogin once
- Note the connector ID from the app details
- Add mapping to config for future migrations
Verbose Logging:
python -m onelogin_migration_cli.app migrate --config config/migration.yaml -vLog Levels:
INFO- Migration progress, summary statisticsDEBUG- API requests, field mappings, detailed operationsWARNING- Skipped items, recoverable errorsERROR- API failures, validation errors with context
- Token bucket algorithm prevents rate limit violations
- Automatic 429 retry with exponential backoff
- Per-API rate limit configuration (source per-minute, OneLogin per-hour)
Smart Worker Calculation:
When max_workers is omitted, the toolkit calculates optimal concurrency:
source_workers = source_rate_limit_per_minute / 150
onelogin_workers = (onelogin_rate_limit_per_hour / 60) / 30
recommended = min(source_workers, onelogin_workers, 16)
Enable multi-threading for large migrations:
concurrency_enabled: true
max_workers: 4 # Or omit for auto-calculationThread Safety:
- All API clients use thread-local sessions
- Progress tracking with mutex locks
- State persistence is atomic
Bulk CSV Export: Generate OneLogin-compatible CSV for manual upload:
python -m onelogin_migration_cli.app migrate --config config/migration.yaml --bulk-user-uploadMain Export:
source_export.json- Complete export with all categories
Timestamped Snapshots:
{source}_users_{timestamp}.json{source}_groups_{timestamp}.json{source}_applications_{timestamp}.json{source}_memberships_{timestamp}.json
When --bulk-user-upload is enabled:
bulk_user_upload_{timestamp}.csv- OneLogin import format- Custom attributes auto-provisioned via API first
migration_state.json- Resume capability- Tracks completed users, groups, apps
- ID mappings for relationships (source ID → OneLogin ID)
- Automatically cleared on successful completion
ModuleNotFoundError: No module named 'onelogin_migration_cli'
The package isn't installed in your virtual environment.
source .venv/bin/activate
./scripts/dev-install.sh
python -m onelogin_migration_cli.app --help422 Unprocessable Entity from OneLogin
Invalid field names or values in the payload.
- Check error logs for specific field names
- Verify custom attributes exist in OneLogin first (
provision-attributes) - Enable verbose logging:
-vflag
401 Unauthorized when assigning roles
Region mismatch or expired token.
- Verify
onelogin.regionmatches your tenant (usoreu) - Check token endpoint:
https://api.{region}.onelogin.com - Verify OAuth credentials are correct
429 Too Many Requests
Rate limit exceeded (retries automatically).
- Reduce
max_workersin config - Verify rate limit settings match your tenant
- Check logs for retry behavior
"cannot modify Account owner"
OneLogin API blocks updates to the tenant owner account. User is skipped automatically — no action required.
Application not migrating (skipped with warning)
No connector ID mapping found.
Add to metadata.application_connectors in config:
metadata:
application_connectors:
"app name":
"saml": 123456Migration is slow
- Enable concurrency:
concurrency_enabled: true
- Use bulk CSV export for users:
python -m onelogin_migration_cli.app migrate --config config/migration.yaml --bulk-user-upload
- Verify rate limits are set correctly
Out of memory errors
- Reduce
page_sizein source config - Process categories separately using
categoriesconfig - Use bulk CSV mode
Install all packages for development:
./scripts/dev-install.sh# All packages
./scripts/test-all.sh
# Individual packages
cd packages/core && pytest tests/ -v
cd packages/cli && pytest tests/ -v
cd packages/gui && pytest tests/ -vCoverage:
cd packages/core
pytest --cov=onelogin_migration_core --cov-report=html./scripts/format-all.sh
./scripts/lint-all.sh.
├── packages/
│ ├── core/ # Core migration library
│ │ ├── src/onelogin_migration_core/
│ │ │ ├── clients.py # API clients
│ │ │ ├── config.py # Configuration
│ │ │ ├── field_mapper.py # Provider field mapping protocol
│ │ │ ├── manager.py # Migration orchestration
│ │ │ ├── progress.py # Progress tracking
│ │ │ ├── credentials.py # Credential management
│ │ │ └── db/ # Database management
│ │ └── tests/
│ ├── cli/ # Command-line interface
│ │ ├── src/onelogin_migration_cli/
│ │ │ ├── app.py # CLI commands (Typer)
│ │ │ ├── credentials.py # Credential commands
│ │ │ ├── database.py # Database commands
│ │ │ └── telemetry.py # Telemetry commands
│ │ └── tests/
│ └── gui/ # Graphical interface
│ ├── src/onelogin_migration_gui/
│ │ ├── main.py # GUI entry point
│ │ ├── steps/ # Wizard steps
│ │ └── dialogs/ # Dialog windows
│ └── tests/
├── scripts/
│ ├── dev-install.sh
│ ├── test-all.sh
│ ├── lint-all.sh
│ └── format-all.sh
├── config/
│ └── migration.template.yaml
└── tools/
├── build_app.sh # macOS build
└── build_app.bat # Windows build
macOS:
./tools/build_app.sh
# Output: dist/OneLogin Migration Tool.appWindows:
tools\build_app.bat
# Output: dist\OneLogin Migration Tool.exeBuild Requirements:
- Python 3.10+ with virtual environment
- All packages installed (
./scripts/dev-install.sh) - PyInstaller 6.0+ (installed automatically by build script)
- Update version in
pyproject.toml - Run full test suite:
./scripts/test-all.sh - Build executables
- Test the bundled app
- Tag release:
git tag vX.Y.Z
Never commit:
- YAML files with credentials (use
credentials migrateto extract them) - Generated exports with real user data
- Vault backup files (
.enc)
Safe to commit:
~/.onelogin-migration/settings.json(non-sensitive settings only)- Sanitized YAML configs (with
*_source: keyringreferences)
- Use system keyring (default) — OS-managed encryption
- Rotate API tokens regularly using
credentials set - Use
credentials testbefore migrations - Enable audit logging for tracking
- Delete old YAML files after migrating to secure storage
See LICENSE file for details.
For issues, feature requests, or questions:
- Check Troubleshooting
- Review existing issues
- Open a new issue with: command used, sanitized config, error logs, expected vs actual behavior