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.pgm; 16 : 17 : import com.google.common.flogger.FluentLogger; 18 : import com.google.gerrit.extensions.config.FactoryModule; 19 : import com.google.gerrit.extensions.registration.DynamicMap; 20 : import com.google.gerrit.lifecycle.LifecycleManager; 21 : import com.google.gerrit.pgm.init.api.ConsoleUI; 22 : import com.google.gerrit.pgm.util.SiteProgram; 23 : import com.google.gerrit.server.account.externalids.DisabledExternalIdCache; 24 : import com.google.gerrit.server.account.externalids.ExternalId; 25 : import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator; 26 : import com.google.gerrit.server.account.externalids.ExternalIdUpsertPreprocessor; 27 : import com.google.gerrit.server.account.externalids.ExternalIds; 28 : import com.google.gerrit.server.config.GerritServerConfig; 29 : import com.google.gerrit.server.extensions.events.GitReferenceUpdated; 30 : import com.google.gerrit.server.git.meta.MetaDataUpdate; 31 : import com.google.gerrit.server.index.account.AccountSchemaDefinitions; 32 : import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck; 33 : import com.google.inject.Inject; 34 : import com.google.inject.Injector; 35 : import com.google.inject.Key; 36 : import com.google.inject.assistedinject.FactoryModuleBuilder; 37 : import java.io.IOException; 38 : import java.util.Collection; 39 : import org.eclipse.jgit.errors.ConfigInvalidException; 40 : import org.eclipse.jgit.lib.Config; 41 : import org.eclipse.jgit.lib.ProgressMonitor; 42 : import org.eclipse.jgit.lib.TextProgressMonitor; 43 : import org.eclipse.jgit.storage.file.FileBasedConfig; 44 : import org.eclipse.jgit.util.FS; 45 : import org.kohsuke.args4j.Option; 46 : 47 : /** 48 : * Changes the case sensitivity of `username:` and `gerrit:` external IDs by recomputing the SHA-1 49 : * sums used as note names. 50 : */ 51 1 : public class ChangeExternalIdCaseSensitivity extends SiteProgram { 52 1 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 53 : 54 : @Option(name = "--batch", usage = "Don't ask for confirmation before migrating.") 55 : private boolean batch; 56 : 57 : @Option(name = "--dryrun", usage = "Do a dryrun of the migration.") 58 : private boolean dryrun; 59 : 60 1 : private final LifecycleManager manager = new LifecycleManager(); 61 1 : private final TextProgressMonitor monitor = new TextProgressMonitor(); 62 : 63 : private Config globalConfig; 64 : private boolean isUserNameCaseInsensitive; 65 : private ConsoleUI ui; 66 : 67 : @Inject private ExternalIds externalIds; 68 : @Inject private ExternalIdCaseSensitivityMigrator.Factory migratorFactory; 69 : 70 : @Override 71 : public int run() throws Exception { 72 1 : mustHaveValidSite(); 73 1 : ui = ConsoleUI.getInstance(batch); 74 : 75 1 : Injector dbInjector = createDbInjector(); 76 1 : manager.add(dbInjector, dbInjector.createChildInjector(NoteDbSchemaVersionCheck.module())); 77 1 : dbInjector 78 1 : .createChildInjector( 79 1 : new FactoryModule() { 80 : @Override 81 : protected void configure() { 82 1 : bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED); 83 1 : install( 84 : new FactoryModuleBuilder() 85 1 : .build(ExternalIdCaseSensitivityMigrator.Factory.class)); 86 1 : factory(MetaDataUpdate.InternalFactory.class); 87 1 : DynamicMap.mapOf(binder(), ExternalIdUpsertPreprocessor.class); 88 : 89 : // The ChangeExternalIdCaseSensitivity program needs to access all external IDs only 90 : // once to update them. After the update they are not accessed again. Hence the 91 : // LocalUsernamesToLowerCase program doesn't benefit from caching external IDs and 92 : // the external ID cache can be disabled. 93 1 : install(DisabledExternalIdCache.module()); 94 1 : } 95 : }) 96 1 : .injectMembers(this); 97 1 : globalConfig = dbInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)); 98 : 99 1 : this.isUserNameCaseInsensitive = 100 1 : globalConfig.getBoolean("auth", "userNameCaseInsensitive", false); 101 : 102 1 : String message = 103 : "auth.userNameCaseInsensitive is set to %b. " 104 : + "External IDs will be migrated to be case %ssensitive. Continue?"; 105 1 : if (!ui.yesno( 106 1 : true, message, isUserNameCaseInsensitive, isUserNameCaseInsensitive ? "" : "in")) { 107 0 : return 0; 108 : } 109 : 110 1 : Collection<ExternalId> todo = externalIds.all(); 111 1 : monitor.beginTask("Converting external ID note names", todo.size()); 112 : 113 1 : manager.start(); 114 : try { 115 1 : migratorFactory 116 1 : .create(!isUserNameCaseInsensitive, dryrun) 117 1 : .migrate(todo, () -> monitor.update(1)); 118 : } finally { 119 1 : manager.stop(); 120 1 : monitor.endTask(); 121 : } 122 : 123 : int exitCode; 124 1 : if (!dryrun) { 125 1 : updateGerritConfig(); 126 : 127 1 : exitCode = reindexAccounts(); 128 : } else { 129 1 : exitCode = 0; 130 : } 131 1 : return exitCode; 132 : } 133 : 134 : private void updateGerritConfig() throws IOException, ConfigInvalidException { 135 1 : logger.atInfo().log("Setting auth.userNameCaseInsensitive to true in gerrit.config."); 136 1 : FileBasedConfig config = 137 : new FileBasedConfig( 138 1 : globalConfig, getSitePath().resolve("etc/gerrit.config").toFile(), FS.DETECTED); 139 1 : config.load(); 140 1 : config.setBoolean("auth", null, "userNameCaseInsensitive", !isUserNameCaseInsensitive); 141 1 : config.save(); 142 1 : } 143 : 144 : private int reindexAccounts() throws Exception { 145 1 : monitor.beginTask("Reindex accounts", ProgressMonitor.UNKNOWN); 146 1 : String[] reindexArgs = { 147 1 : "--site-path", getSitePath().toString(), "--index", AccountSchemaDefinitions.NAME 148 : }; 149 1 : logger.atInfo().log( 150 1 : "Migration complete, reindexing accounts with: reindex %s", String.join(" ", reindexArgs)); 151 1 : Reindex reindexPgm = new Reindex(); 152 1 : int exitCode = reindexPgm.main(reindexArgs); 153 1 : monitor.endTask(); 154 1 : return exitCode; 155 : } 156 : }