| // 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; |
| } |
| } |
| } |