Line data Source code
1 : // Copyright (C) 2017 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 com.google.common.annotations.VisibleForTesting; 18 : import com.google.common.collect.ImmutableSet; 19 : import com.google.gerrit.common.Nullable; 20 : import com.google.gerrit.entities.RefNames; 21 : import com.google.gerrit.metrics.Description; 22 : import com.google.gerrit.metrics.Description.Units; 23 : import com.google.gerrit.metrics.MetricMaker; 24 : import com.google.gerrit.metrics.Timer0; 25 : import com.google.gerrit.server.config.AllUsersName; 26 : import com.google.gerrit.server.config.AuthConfig; 27 : import com.google.gerrit.server.git.GitRepositoryManager; 28 : import com.google.inject.Inject; 29 : import com.google.inject.Singleton; 30 : import java.io.IOException; 31 : import java.util.Optional; 32 : import org.eclipse.jgit.errors.ConfigInvalidException; 33 : import org.eclipse.jgit.lib.ObjectId; 34 : import org.eclipse.jgit.lib.Ref; 35 : import org.eclipse.jgit.lib.Repository; 36 : import org.eclipse.jgit.notes.NoteMap; 37 : import org.eclipse.jgit.revwalk.RevWalk; 38 : 39 : /** 40 : * Class to read external IDs from NoteDb. 41 : * 42 : * <p>In NoteDb external IDs are stored in the All-Users repository in a Git Notes branch called 43 : * refs/meta/external-ids where the sha1 of the external ID is used as note name. Each note content 44 : * is a git config file that contains an external ID. It has exactly one externalId subsection with 45 : * an accountId and optionally email and password: 46 : * 47 : * <pre> 48 : * [externalId "username:jdoe"] 49 : * accountId = 1003407 50 : * email = jdoe@example.com 51 : * password = bcrypt:4:LCbmSBDivK/hhGVQMfkDpA==:XcWn0pKYSVU/UJgOvhidkEtmqCp6oKB7 52 : * </pre> 53 : */ 54 : @Singleton 55 : public class ExternalIdReader { 56 : public static ObjectId readRevision(Repository repo) throws IOException { 57 151 : Ref ref = repo.exactRef(RefNames.REFS_EXTERNAL_IDS); 58 151 : return ref != null ? ref.getObjectId() : ObjectId.zeroId(); 59 : } 60 : 61 : public static NoteMap readNoteMap(RevWalk rw, ObjectId rev) throws IOException { 62 2 : if (!rev.equals(ObjectId.zeroId())) { 63 2 : return NoteMap.read(rw.getObjectReader(), rw.parseCommit(rev)); 64 : } 65 0 : return NoteMap.newEmptyMap(); 66 : } 67 : 68 : private final GitRepositoryManager repoManager; 69 : private final AllUsersName allUsersName; 70 152 : private boolean failOnLoad = false; 71 : private final Timer0 readAllLatency; 72 : private final Timer0 readSingleLatency; 73 : private final ExternalIdFactory externalIdFactory; 74 : private final AuthConfig authConfig; 75 : 76 : @Inject 77 : ExternalIdReader( 78 : GitRepositoryManager repoManager, 79 : AllUsersName allUsersName, 80 : MetricMaker metricMaker, 81 : ExternalIdFactory externalIdFactory, 82 152 : AuthConfig authConfig) { 83 152 : this.repoManager = repoManager; 84 152 : this.allUsersName = allUsersName; 85 152 : this.readAllLatency = 86 152 : metricMaker.newTimer( 87 : "notedb/read_all_external_ids_latency", 88 : new Description("Latency for reading all external IDs from NoteDb.") 89 152 : .setCumulative() 90 152 : .setUnit(Units.MILLISECONDS)); 91 152 : this.readSingleLatency = 92 152 : metricMaker.newTimer( 93 : "notedb/read_single_external_id_latency", 94 : new Description("Latency for reading a single external ID from NoteDb.") 95 152 : .setCumulative() 96 152 : .setUnit(Units.MILLISECONDS)); 97 152 : this.externalIdFactory = externalIdFactory; 98 152 : this.authConfig = authConfig; 99 152 : } 100 : 101 : @VisibleForTesting 102 : public void setFailOnLoad(boolean failOnLoad) { 103 1 : this.failOnLoad = failOnLoad; 104 1 : } 105 : 106 : public void checkReadEnabled() throws IOException { 107 151 : if (failOnLoad) { 108 1 : throw new IOException("Reading from external IDs is disabled"); 109 : } 110 151 : } 111 : 112 : ObjectId readRevision() throws IOException { 113 151 : try (Repository repo = repoManager.openRepository(allUsersName)) { 114 151 : return readRevision(repo); 115 : } 116 : } 117 : 118 : /** Reads and returns all external IDs. */ 119 : ImmutableSet<ExternalId> all() throws IOException, ConfigInvalidException { 120 3 : checkReadEnabled(); 121 : 122 3 : try (Timer0.Context ctx = readAllLatency.start(); 123 3 : Repository repo = repoManager.openRepository(allUsersName)) { 124 3 : return ExternalIdNotes.loadReadOnly( 125 : allUsersName, 126 : repo, 127 : null, 128 : externalIdFactory, 129 3 : authConfig.isUserNameCaseInsensitiveMigrationMode()) 130 3 : .all(); 131 : } 132 : } 133 : 134 : /** 135 : * Reads and returns all external IDs from the specified revision of the {@code 136 : * refs/meta/external-ids} branch. 137 : * 138 : * @param rev the revision from which the external IDs should be read, if {@code null} the 139 : * external IDs are read from the current tip, if {@link ObjectId#zeroId()} it's assumed that 140 : * the {@code refs/meta/external-ids} branch doesn't exist and the loaded external IDs will be 141 : * empty 142 : * @return all external IDs that were read from the specified revision 143 : */ 144 : ImmutableSet<ExternalId> all(@Nullable ObjectId rev) throws IOException, ConfigInvalidException { 145 151 : checkReadEnabled(); 146 : 147 151 : try (Timer0.Context ctx = readAllLatency.start(); 148 151 : Repository repo = repoManager.openRepository(allUsersName)) { 149 151 : return ExternalIdNotes.loadReadOnly( 150 : allUsersName, 151 : repo, 152 : rev, 153 : externalIdFactory, 154 151 : authConfig.isUserNameCaseInsensitiveMigrationMode()) 155 151 : .all(); 156 : } 157 : } 158 : 159 : /** Reads and returns the specified external ID. */ 160 : Optional<ExternalId> get(ExternalId.Key key) throws IOException, ConfigInvalidException { 161 0 : checkReadEnabled(); 162 : 163 0 : try (Timer0.Context ctx = readSingleLatency.start(); 164 0 : Repository repo = repoManager.openRepository(allUsersName)) { 165 0 : return ExternalIdNotes.loadReadOnly( 166 : allUsersName, 167 : repo, 168 : null, 169 : externalIdFactory, 170 0 : authConfig.isUserNameCaseInsensitiveMigrationMode()) 171 0 : .get(key); 172 : } 173 : } 174 : 175 : /** Reads and returns the specified external ID from the given revision. */ 176 : Optional<ExternalId> get(ExternalId.Key key, ObjectId rev) 177 : throws IOException, ConfigInvalidException { 178 0 : checkReadEnabled(); 179 : 180 0 : try (Timer0.Context ctx = readSingleLatency.start(); 181 0 : Repository repo = repoManager.openRepository(allUsersName)) { 182 0 : return ExternalIdNotes.loadReadOnly( 183 : allUsersName, 184 : repo, 185 : rev, 186 : externalIdFactory, 187 0 : authConfig.isUserNameCaseInsensitiveMigrationMode()) 188 0 : .get(key); 189 : } 190 : } 191 : }