Реалізація переносу користувачів та паролів при міграції сайту
Міграція користувачів — технічно складний процес через несумісність алгоритмів хешування паролів між різними платформами. Правильний підхід зберігає користувачів від необхідності скидання паролів.
Проблема несумісності гешей
| Платформа | Алгоритм | Формат |
|---|---|---|
| WordPress | phpass (md5-based) | $P$BHash... |
| Drupal 7 | sha512 + сіль | $S$5Hash... |
| Laravel | bcrypt | $2y$10$Hash... |
| Django | PBKDF2 SHA256 | pbkdf2_sha256$N$salt$hash |
| PHP legacy | MD5 | 32 символа hex |
| bcrypt | bcrypt | $2a$10$Hash... |
Стратегія 1: Lazy Міграція (Переважна)
Геші переносяться як є. При першому вході користувача перевіряється старий алгоритм; при успіху геш перехешується новим алгоритмом.
# models/user.py
class User(BaseModel):
password_hash: str
password_algorithm: str # 'bcrypt', 'phpass', 'sha512', 'legacy_md5'
def verify_password(self, plain_password: str) -> bool:
if self.password_algorithm == 'bcrypt':
return bcrypt.checkpw(plain_password.encode(), self.password_hash.encode())
elif self.password_algorithm == 'phpass':
return phpass_check(plain_password, self.password_hash)
elif self.password_algorithm == 'legacy_md5':
return hashlib.md5(plain_password.encode()).hexdigest() == self.password_hash
return False
def upgrade_password_hash(self, plain_password: str):
"""Перехешувати при успішному вході"""
new_hash = bcrypt.hashpw(plain_password.encode(), bcrypt.gensalt(rounds=12))
self.password_hash = new_hash.decode()
self.password_algorithm = 'bcrypt'
db.save(self)
Обробка входу:
def login(email: str, password: str):
user = db.get_user_by_email(email)
if not user:
return None
if user.verify_password(password):
# Оновити геш якщо використовується застарілий алгоритм
if user.password_algorithm != 'bcrypt':
user.upgrade_password_hash(password)
return create_session(user)
return None
ETL: Перенесення користувачів
def migrate_users_from_wordpress(wp_db, new_db):
cursor = wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT
u.ID, u.user_login, u.user_pass, u.user_email,
u.user_registered, u.display_name,
um.meta_value as first_name
FROM wp_users u
LEFT JOIN wp_usermeta um ON u.ID = um.user_id AND um.meta_key = 'first_name'
ORDER BY u.ID
""")
migrated = 0
skipped = 0
for wp_user in cursor.fetchall():
# Перевірити: уже перенесений?
existing = new_db.get_user_by_email(wp_user['user_email'])
if existing:
skipped += 1
continue
algorithm = detect_wp_hash_algorithm(wp_user['user_pass'])
new_db.create_user({
'username': wp_user['user_login'],
'email': wp_user['user_email'],
'password_hash': wp_user['user_pass'],
'password_algorithm': algorithm,
'display_name': wp_user['display_name'],
'created_at': wp_user['user_registered'],
'legacy_id': wp_user['ID'],
})
migrated += 1
print(f"Migrated: {migrated}, Skipped: {skipped}")
def detect_wp_hash_algorithm(hash_val):
if hash_val.startswith('$P$') or hash_val.startswith('$H$'):
return 'phpass'
if hash_val.startswith('$2y$') or hash_val.startswith('$2a$'):
return 'bcrypt'
if len(hash_val) == 32:
return 'legacy_md5'
return 'unknown'
Примусовий скид паролів для застарілих алгоритмів
Якщо підтримка legacy алгоритмів небажана — сповістити користувачів про скидання:
def send_password_reset_for_legacy_users():
users = db.query(
"SELECT * FROM users WHERE password_algorithm IN ('legacy_md5', 'sha1')"
)
for user in users:
token = generate_secure_token()
db.save_reset_token(user.id, token, expires_in=7*24*3600)
send_email(
to=user.email,
subject="Необхідно оновити пароль",
template="password_reset_migration",
vars={
'name': user.display_name,
'reset_url': f"https://site.com/reset?token={token}",
'deadline': '7 днів'
}
)
Ролі та дозволи
ROLE_MAP = {
# WordPress → Custom CMS
'administrator': 'admin',
'editor': 'editor',
'author': 'author',
'contributor': 'contributor',
'subscriber': 'user',
}
def migrate_user_roles(wp_db, new_db):
cursor = wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT user_id, meta_value as capabilities
FROM wp_usermeta WHERE meta_key = 'wp_capabilities'
""")
for row in cursor.fetchall():
caps = php_unserialize(row['capabilities'])
wp_role = list(caps.keys())[0] if caps else 'subscriber'
new_role = ROLE_MAP.get(wp_role, 'user')
new_db.update_user_role(row['user_id'], new_role)
Тривалість виконання
Міграція користувачів з lazy password migration та маппінгом ролей — 2–3 робочих дні.







