diff --git a/migrations.py b/migrations.py index 84fcd22822231e148c3f3a61a9d53b3cac18dfd4..07cfd4b537dc971bf3bfea44f4a70804999c8031 100644 --- a/migrations.py +++ b/migrations.py @@ -1,5 +1,6 @@ import couchconnection import json +import re (db, conn) = couchconnection.arsnova_connection("/etc/arsnova/arsnova.properties") @@ -195,6 +196,85 @@ def migrate(migration): print bump(current_version) if current_version == 8: + print "Migrating DB and LDAP user IDs to lowercase..." + conn.request("GET", db_url + "/_design/user/_view/all") + res = conn.getresponse() + doc = json.loads(res.read()) + affected_users = {} + unaffected_users = [] + bulk_docs = [] + + # Look for user documents where user ID is not in lowercase + # 1) Delete document if account has not been activated + # 2) Lock account if a lowercase version already exists + # 3) Convert user ID to lowercase if only one captitalization exists + for user_doc in doc["rows"]: + if user_doc["key"] != user_doc["key"].lower(): + # create a list of user documents since there might be multiple + # items for different captitalizations + affected_users.setdefault(user_doc["key"].lower(), []).append(user_doc["value"]) + else: + unaffected_users.append(user_doc["key"]) + for uid, users in affected_users.iteritems(): + migration_targets = [] + for user in users: + if "activationKey" in user: + print "User %s has not been activated. Deleting document %s..." % (user["username"], user["_id"]) + conn.delete(db_url + "/" + user["_id"]) + elif uid in unaffected_users: + print "Migration target exists. Locking duplicate user %s (document %s)..." % (user["username"], user["_id"]) + user["locked"] = True + bulk_docs.append(user) + else: + migration_targets.append(user) + if len(migration_targets) > 1: + print "Cannot migrate some users automatically. Conflicting duplicate users found:" + for user in migration_targets: + print "Locking user %s (document %s)..." % (user["username"], user["_id"]) + user["locked"] = True + bulk_docs.append(user) + elif migration_targets: + print "Migrating user %s (document %s)..." % (user["username"], user["_id"]) + user["username"] = uid + bulk_docs.append(user) + + # Look for data where assigned user's ID is not in lowercase + # 1) Migrate if user ID was affected by previous migration step + # 2) Exclude Facebook and Google account IDs + # 3) Exclude guest account IDs + # 4) Migrate all remaining IDs (LDAP) + def reassign_data(type, user_prop): + print "Reassigning %s data to migrated users..." % type + migration_view = "{ \"map\": \"function(doc) { function check(doc, type, uid) { return doc.type === type && uid !== uid.toLowerCase() && uid.indexOf('Guest') !== 0; } if (check(doc, '%s', doc.%s)) { emit(doc._id, doc); }}\" }" % (type, user_prop) + res = conn.temp_view(db_url, migration_view) + doc = json.loads(res.read()) + print "Documents: %d" % len(doc["rows"]) + for affected_doc in doc["rows"]: + val = affected_doc["value"] + print affected_doc["id"], val[user_prop] + # exclude Facebook and Google accounts from migration (might be + # redundant) + if (not re.match("https?:", val[user_prop]) and not "@" in val[user_prop]) or val[user_prop].lower() in affected_users: + val[user_prop] = val[user_prop].lower() + bulk_docs.append(val) + else: + print "Skipped %s (Facebook/Google account)" % val[user_prop] + + reassign_data("session", "creator") + reassign_data("interposed_question", "creator") + reassign_data("skill_question_answer", "user") + reassign_data("logged_in", "user") + reassign_data("motdlist", "username") + + # bulk update users and assignments + res = conn.json_post(bulk_url, json.dumps({"docs": bulk_docs})) + if res: + res.read() + # bump database version + current_version = 9 + print bump(current_version) + + if current_version == 9: # Next migration goes here pass