blob: a6ee366c70d5ab5fd14644836a31d864cd406c2e [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with 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.
package com.google.gerrit.server.account.externalids;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
public class ExternalIdCaseSensitivityMigrator {
public static class ExternalIdCaseSensitivityMigratorModule extends AbstractModule {
@Override
public void configure() {
install(new FactoryModuleBuilder().build(ExternalIdCaseSensitivityMigrator.Factory.class));
}
}
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public interface Factory {
ExternalIdCaseSensitivityMigrator create(
@Assisted("isUserNameCaseInsensitive") Boolean isUserNameCaseInsensitive,
@Assisted("dryRun") Boolean dryRun);
}
private GitRepositoryManager repoManager;
private AllUsersName allUsersName;
private Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
private ExternalIdNotes.FactoryNoReindex externalIdNotesFactory;
private ExternalIdFactory externalIdFactory;
private Boolean isUserNameCaseInsensitive;
private Boolean dryRun;
@Inject
public ExternalIdCaseSensitivityMigrator(
GitRepositoryManager repoManager,
AllUsersName allUsersName,
Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
ExternalIdNotes.FactoryNoReindex externalIdNotesFactory,
ExternalIdFactory externalIdFactory,
@Assisted("isUserNameCaseInsensitive") Boolean isUserNameCaseInsensitive,
@Assisted("dryRun") Boolean dryRun) {
this.repoManager = repoManager;
this.allUsersName = allUsersName;
this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
this.externalIdNotesFactory = externalIdNotesFactory;
this.externalIdFactory = externalIdFactory;
this.isUserNameCaseInsensitive = isUserNameCaseInsensitive;
this.dryRun = dryRun;
}
private void recomputeExternalIdNoteId(ExternalIdNotes extIdNotes, ExternalId extId)
throws DuplicateKeyException, IOException {
if (extId.isScheme(SCHEME_GERRIT) || extId.isScheme(SCHEME_USERNAME)) {
ExternalIdKeyFactory keyFactory =
new ExternalIdKeyFactory(
new ExternalIdKeyFactory.Config() {
@Override
public boolean isUserNameCaseInsensitive() {
return isUserNameCaseInsensitive;
}
});
ExternalId.Key updatedKey = keyFactory.create(extId.key().scheme(), extId.key().id());
ExternalId.Key oldKey =
keyFactory.create(extId.key().scheme(), extId.key().id(), !isUserNameCaseInsensitive);
if (!oldKey.sha1().getName().equals(updatedKey.sha1().getName())
&& !extId.key().sha1().getName().equals(updatedKey.sha1().getName())) {
logger.atInfo().log("Converting note name of external ID: %s", oldKey);
ExternalId updatedExtId =
externalIdFactory.create(
updatedKey, extId.accountId(), extId.email(), extId.password(), extId.blobId());
ExternalId oldExtId =
externalIdFactory.create(
oldKey, extId.accountId(), extId.email(), extId.password(), extId.blobId());
extIdNotes.replace(
Collections.singleton(oldExtId),
Collections.singleton(updatedExtId),
(externalId) -> externalId.key().sha1());
}
}
}
public void migrate(Collection<ExternalId> todo, Runnable monitor)
throws RepositoryNotFoundException, IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
ExternalIdNotes extIdNotes = externalIdNotesFactory.load(repo);
for (ExternalId extId : todo) {
recomputeExternalIdNoteId(extIdNotes, extId);
monitor.run();
}
if (!dryRun) {
try (MetaDataUpdate metaDataUpdate =
metaDataUpdateServerFactory.get().create(allUsersName)) {
metaDataUpdate.setMessage(
String.format(
"Migration to case %ssensitive usernames",
isUserNameCaseInsensitive ? "" : "in"));
extIdNotes.commit(metaDataUpdate);
} catch (Exception e) {
logger.atSevere().withCause(e).log("%s", e.getMessage());
}
}
} catch (DuplicateExternalIdKeyException e) {
logger.atSevere().withCause(e).log("%s", e.getMessage());
throw e;
}
}
}