Line data Source code
1 : // Copyright (C) 2021 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.account.externalids; 16 : 17 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT; 18 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; 19 : 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.exceptions.DuplicateKeyException; 22 : import com.google.gerrit.server.config.AllUsersName; 23 : import com.google.gerrit.server.git.GitRepositoryManager; 24 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 25 : import com.google.inject.AbstractModule; 26 : import com.google.inject.Inject; 27 : import com.google.inject.Provider; 28 : import com.google.inject.assistedinject.Assisted; 29 : import com.google.inject.assistedinject.FactoryModuleBuilder; 30 : import java.io.IOException; 31 : import java.util.Collection; 32 : import java.util.Collections; 33 : import org.eclipse.jgit.errors.ConfigInvalidException; 34 : import org.eclipse.jgit.errors.RepositoryNotFoundException; 35 : import org.eclipse.jgit.lib.Repository; 36 : 37 : public class ExternalIdCaseSensitivityMigrator { 38 : 39 138 : public static class ExternalIdCaseSensitivityMigratorModule extends AbstractModule { 40 : @Override 41 : public void configure() { 42 138 : install(new FactoryModuleBuilder().build(ExternalIdCaseSensitivityMigrator.Factory.class)); 43 138 : } 44 : } 45 : 46 2 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 47 : 48 : public interface Factory { 49 : ExternalIdCaseSensitivityMigrator create( 50 : @Assisted("isUserNameCaseInsensitive") Boolean isUserNameCaseInsensitive, 51 : @Assisted("dryRun") Boolean dryRun); 52 : } 53 : 54 : private GitRepositoryManager repoManager; 55 : private AllUsersName allUsersName; 56 : private Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory; 57 : private ExternalIdNotes.FactoryNoReindex externalIdNotesFactory; 58 : 59 : private ExternalIdFactory externalIdFactory; 60 : private Boolean isUserNameCaseInsensitive; 61 : private Boolean dryRun; 62 : 63 : @Inject 64 : public ExternalIdCaseSensitivityMigrator( 65 : GitRepositoryManager repoManager, 66 : AllUsersName allUsersName, 67 : Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory, 68 : ExternalIdNotes.FactoryNoReindex externalIdNotesFactory, 69 : ExternalIdFactory externalIdFactory, 70 : @Assisted("isUserNameCaseInsensitive") Boolean isUserNameCaseInsensitive, 71 2 : @Assisted("dryRun") Boolean dryRun) { 72 2 : this.repoManager = repoManager; 73 2 : this.allUsersName = allUsersName; 74 2 : this.metaDataUpdateServerFactory = metaDataUpdateServerFactory; 75 2 : this.externalIdNotesFactory = externalIdNotesFactory; 76 2 : this.externalIdFactory = externalIdFactory; 77 : 78 2 : this.isUserNameCaseInsensitive = isUserNameCaseInsensitive; 79 2 : this.dryRun = dryRun; 80 2 : } 81 : 82 : private void recomputeExternalIdNoteId(ExternalIdNotes extIdNotes, ExternalId extId) 83 : throws DuplicateKeyException, IOException { 84 2 : if (extId.isScheme(SCHEME_GERRIT) || extId.isScheme(SCHEME_USERNAME)) { 85 2 : ExternalIdKeyFactory keyFactory = 86 : new ExternalIdKeyFactory( 87 2 : new ExternalIdKeyFactory.Config() { 88 : @Override 89 : public boolean isUserNameCaseInsensitive() { 90 2 : return isUserNameCaseInsensitive; 91 : } 92 : }); 93 2 : ExternalId.Key updatedKey = keyFactory.create(extId.key().scheme(), extId.key().id()); 94 2 : ExternalId.Key oldKey = 95 2 : keyFactory.create(extId.key().scheme(), extId.key().id(), !isUserNameCaseInsensitive); 96 2 : if (!oldKey.sha1().getName().equals(updatedKey.sha1().getName()) 97 2 : && !extId.key().sha1().getName().equals(updatedKey.sha1().getName())) { 98 2 : logger.atInfo().log("Converting note name of external ID: %s", oldKey); 99 2 : ExternalId updatedExtId = 100 2 : externalIdFactory.create( 101 2 : updatedKey, extId.accountId(), extId.email(), extId.password(), extId.blobId()); 102 2 : ExternalId oldExtId = 103 2 : externalIdFactory.create( 104 2 : oldKey, extId.accountId(), extId.email(), extId.password(), extId.blobId()); 105 2 : extIdNotes.replace( 106 2 : Collections.singleton(oldExtId), 107 2 : Collections.singleton(updatedExtId), 108 2 : (externalId) -> externalId.key().sha1()); 109 : } 110 : } 111 2 : } 112 : 113 : public void migrate(Collection<ExternalId> todo, Runnable monitor) 114 : throws RepositoryNotFoundException, IOException, ConfigInvalidException { 115 2 : try (Repository repo = repoManager.openRepository(allUsersName)) { 116 2 : ExternalIdNotes extIdNotes = externalIdNotesFactory.load(repo); 117 2 : for (ExternalId extId : todo) { 118 2 : recomputeExternalIdNoteId(extIdNotes, extId); 119 2 : monitor.run(); 120 2 : } 121 2 : if (!dryRun) { 122 2 : try (MetaDataUpdate metaDataUpdate = 123 2 : metaDataUpdateServerFactory.get().create(allUsersName)) { 124 2 : metaDataUpdate.setMessage( 125 2 : String.format( 126 : "Migration to case %ssensitive usernames", 127 2 : isUserNameCaseInsensitive ? "" : "in")); 128 2 : extIdNotes.commit(metaDataUpdate); 129 0 : } catch (Exception e) { 130 0 : logger.atSevere().withCause(e).log("%s", e.getMessage()); 131 2 : } 132 : } 133 2 : } catch (DuplicateExternalIdKeyException e) { 134 2 : logger.atSevere().withCause(e).log("%s", e.getMessage()); 135 2 : throw e; 136 2 : } 137 2 : } 138 : }