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 static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; 18 : import static java.util.stream.Collectors.joining; 19 : 20 : import com.google.common.collect.ListMultimap; 21 : import com.google.common.collect.MultimapBuilder; 22 : import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo; 23 : import com.google.gerrit.server.account.AccountCache; 24 : import com.google.gerrit.server.account.HashedPassword; 25 : import com.google.gerrit.server.config.AllUsersName; 26 : import com.google.gerrit.server.git.GitRepositoryManager; 27 : import com.google.gerrit.server.mail.send.OutgoingEmailValidator; 28 : import com.google.inject.Inject; 29 : import com.google.inject.Singleton; 30 : import java.io.IOException; 31 : import java.util.ArrayList; 32 : import java.util.List; 33 : import org.eclipse.jgit.errors.ConfigInvalidException; 34 : import org.eclipse.jgit.lib.ObjectId; 35 : import org.eclipse.jgit.lib.Repository; 36 : import org.eclipse.jgit.notes.Note; 37 : import org.eclipse.jgit.notes.NoteMap; 38 : import org.eclipse.jgit.revwalk.RevWalk; 39 : 40 : @Singleton 41 : public class ExternalIdsConsistencyChecker { 42 : private final GitRepositoryManager repoManager; 43 : private final AllUsersName allUsers; 44 : private final AccountCache accountCache; 45 : private final OutgoingEmailValidator validator; 46 : private final ExternalIdFactory externalIdFactory; 47 : 48 : @Inject 49 : ExternalIdsConsistencyChecker( 50 : GitRepositoryManager repoManager, 51 : AllUsersName allUsers, 52 : AccountCache accountCache, 53 : OutgoingEmailValidator validator, 54 146 : ExternalIdFactory externalIdFactory) { 55 146 : this.repoManager = repoManager; 56 146 : this.allUsers = allUsers; 57 146 : this.accountCache = accountCache; 58 146 : this.validator = validator; 59 146 : this.externalIdFactory = externalIdFactory; 60 146 : } 61 : 62 : public List<ConsistencyProblemInfo> check() throws IOException, ConfigInvalidException { 63 1 : try (Repository repo = repoManager.openRepository(allUsers)) { 64 1 : return check(ExternalIdNotes.loadReadOnly(allUsers, repo, null, externalIdFactory, false)); 65 : } 66 : } 67 : 68 : public List<ConsistencyProblemInfo> check(ObjectId rev) 69 : throws IOException, ConfigInvalidException { 70 1 : try (Repository repo = repoManager.openRepository(allUsers)) { 71 1 : return check(ExternalIdNotes.loadReadOnly(allUsers, repo, rev, externalIdFactory, false)); 72 : } 73 : } 74 : 75 : private List<ConsistencyProblemInfo> check(ExternalIdNotes extIdNotes) throws IOException { 76 1 : List<ConsistencyProblemInfo> problems = new ArrayList<>(); 77 : 78 1 : ListMultimap<String, ExternalId> emails = MultimapBuilder.hashKeys().arrayListValues().build(); 79 : 80 1 : try (RevWalk rw = new RevWalk(extIdNotes.getRepository())) { 81 1 : NoteMap noteMap = extIdNotes.getNoteMap(); 82 1 : for (Note note : noteMap) { 83 1 : byte[] raw = ExternalIdNotes.readNoteData(rw, note.getData()); 84 : try { 85 1 : ExternalId extId = externalIdFactory.parse(note.getName(), raw, note.getData()); 86 1 : problems.addAll(validateExternalId(extId)); 87 : 88 1 : if (extId.email() != null) { 89 1 : String email = extId.email(); 90 1 : if (emails.get(email).stream() 91 1 : .noneMatch(e -> e.accountId().get() == extId.accountId().get())) { 92 1 : emails.put(email, extId); 93 : } 94 : } 95 1 : } catch (ConfigInvalidException e) { 96 1 : addError(String.format(e.getMessage()), problems); 97 1 : } 98 1 : } 99 : } 100 : 101 1 : emails.asMap().entrySet().stream() 102 1 : .filter(e -> e.getValue().size() > 1) 103 1 : .forEach( 104 : e -> 105 1 : addError( 106 1 : String.format( 107 : "Email '%s' is not unique, it's used by the following external IDs: %s", 108 1 : e.getKey(), 109 1 : e.getValue().stream() 110 1 : .map(k -> "'" + k.key().get() + "'") 111 1 : .sorted() 112 1 : .collect(joining(", "))), 113 : problems)); 114 : 115 1 : return problems; 116 : } 117 : 118 : private List<ConsistencyProblemInfo> validateExternalId(ExternalId extId) { 119 1 : List<ConsistencyProblemInfo> problems = new ArrayList<>(); 120 : 121 1 : if (!accountCache.get(extId.accountId()).isPresent()) { 122 1 : addError( 123 1 : String.format( 124 : "External ID '%s' belongs to account that doesn't exist: %s", 125 1 : extId.key().get(), extId.accountId().get()), 126 : problems); 127 : } 128 : 129 1 : if (extId.email() != null && !validator.isValid(extId.email())) { 130 1 : addError( 131 1 : String.format( 132 1 : "External ID '%s' has an invalid email: %s", extId.key().get(), extId.email()), 133 : problems); 134 : } 135 : 136 1 : if (extId.password() != null && extId.isScheme(SCHEME_USERNAME)) { 137 : try { 138 1 : HashedPassword.decode(extId.password()); 139 1 : } catch (HashedPassword.DecoderException e) { 140 1 : addError( 141 1 : String.format( 142 1 : "External ID '%s' has an invalid password: %s", extId.key().get(), e.getMessage()), 143 : problems); 144 1 : } 145 : } 146 : 147 1 : return problems; 148 : } 149 : 150 : private static void addError(String error, List<ConsistencyProblemInfo> problems) { 151 1 : problems.add(new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, error)); 152 1 : } 153 : }