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.group.db; 16 : 17 : import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.error; 18 : import static com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo.warning; 19 : 20 : import com.google.gerrit.entities.Account; 21 : import com.google.gerrit.entities.AccountGroup; 22 : import com.google.gerrit.entities.InternalGroup; 23 : import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo; 24 : import com.google.gerrit.server.account.AccountState; 25 : import com.google.gerrit.server.account.Accounts; 26 : import com.google.gerrit.server.account.GroupBackend; 27 : import com.google.gerrit.server.config.AllUsersName; 28 : import com.google.gerrit.server.git.GitRepositoryManager; 29 : import com.google.inject.Inject; 30 : import com.google.inject.Singleton; 31 : import java.io.IOException; 32 : import java.util.ArrayList; 33 : import java.util.HashSet; 34 : import java.util.LinkedHashSet; 35 : import java.util.List; 36 : import java.util.Map; 37 : import java.util.Objects; 38 : import java.util.Optional; 39 : import java.util.Set; 40 : import org.eclipse.jgit.errors.ConfigInvalidException; 41 : import org.eclipse.jgit.lib.Repository; 42 : 43 : /** 44 : * Checks individual groups for oddities, such as cycles, non-existent subgroups, etc. Only works if 45 : * we are writing to NoteDb. 46 : */ 47 : @Singleton 48 : public class GroupsConsistencyChecker { 49 : private final AllUsersName allUsersName; 50 : private final GroupBackend groupBackend; 51 : private final Accounts accounts; 52 : private final GitRepositoryManager repoManager; 53 : private final GroupsNoteDbConsistencyChecker globalChecker; 54 : 55 : @Inject 56 : GroupsConsistencyChecker( 57 : AllUsersName allUsersName, 58 : GroupBackend groupBackend, 59 : Accounts accounts, 60 : GitRepositoryManager repositoryManager, 61 138 : GroupsNoteDbConsistencyChecker globalChecker) { 62 138 : this.allUsersName = allUsersName; 63 138 : this.groupBackend = groupBackend; 64 138 : this.accounts = accounts; 65 138 : this.repoManager = repositoryManager; 66 138 : this.globalChecker = globalChecker; 67 138 : } 68 : 69 : /** Checks that all internal group references exist, and that no groups have cycles. */ 70 : public List<ConsistencyProblemInfo> check() throws IOException { 71 1 : try (Repository repo = repoManager.openRepository(allUsersName)) { 72 1 : GroupsNoteDbConsistencyChecker.Result result = globalChecker.check(repo); 73 1 : if (!result.problems.isEmpty()) { 74 1 : return result.problems; 75 : } 76 : 77 1 : for (InternalGroup g : result.uuidToGroupMap.values()) { 78 1 : result.problems.addAll(checkGroup(g, result.uuidToGroupMap)); 79 1 : } 80 : 81 1 : return result.problems; 82 1 : } 83 : } 84 : 85 : /** Checks the metadata for a single group for problems. */ 86 : private List<ConsistencyProblemInfo> checkGroup( 87 : InternalGroup g, Map<AccountGroup.UUID, InternalGroup> byUUID) throws IOException { 88 1 : List<ConsistencyProblemInfo> problems = new ArrayList<>(); 89 : 90 1 : problems.addAll(checkCycle(g, byUUID)); 91 : 92 1 : if (byUUID.get(g.getOwnerGroupUUID()) == null 93 1 : && groupBackend.get(g.getOwnerGroupUUID()) == null) { 94 1 : problems.add( 95 1 : error( 96 : "group %s (%s) has nonexistent owner group %s", 97 1 : g.getName(), g.getGroupUUID(), g.getOwnerGroupUUID())); 98 : } 99 : 100 1 : for (AccountGroup.UUID subUuid : g.getSubgroups()) { 101 1 : if (byUUID.get(subUuid) == null && groupBackend.get(subUuid) == null) { 102 1 : problems.add( 103 1 : error( 104 : "group %s (%s) has nonexistent subgroup %s", 105 1 : g.getName(), g.getGroupUUID(), subUuid)); 106 : } 107 1 : } 108 : 109 1 : for (Account.Id id : g.getMembers().asList()) { 110 : Optional<AccountState> account; 111 : try { 112 1 : account = accounts.get(id); 113 0 : } catch (ConfigInvalidException e) { 114 0 : problems.add( 115 0 : error( 116 : "group %s (%s) has member %s with invalid configuration: %s", 117 0 : g.getName(), g.getGroupUUID(), id, e.getMessage())); 118 0 : continue; 119 1 : } 120 1 : if (!account.isPresent()) { 121 1 : problems.add( 122 1 : error("group %s (%s) has nonexistent member %s", g.getName(), g.getGroupUUID(), id)); 123 : } 124 1 : } 125 1 : return problems; 126 : } 127 : 128 : /** checkCycle walks through root's subgroups recursively, and checks for cycles. */ 129 : private List<ConsistencyProblemInfo> checkCycle( 130 : InternalGroup root, Map<AccountGroup.UUID, InternalGroup> byUUID) { 131 1 : List<ConsistencyProblemInfo> problems = new ArrayList<>(); 132 1 : Set<InternalGroup> todo = new LinkedHashSet<>(); 133 1 : Set<InternalGroup> seen = new HashSet<>(); 134 : 135 1 : todo.add(root); 136 1 : while (!todo.isEmpty()) { 137 1 : InternalGroup t = todo.iterator().next(); 138 1 : todo.remove(t); 139 : 140 1 : if (seen.contains(t)) { 141 1 : continue; 142 : } 143 1 : seen.add(t); 144 : 145 : // We don't check for owner cycles, since those are normal in self-administered groups. 146 1 : for (AccountGroup.UUID subUuid : t.getSubgroups()) { 147 1 : InternalGroup g = byUUID.get(subUuid); 148 1 : if (g == null) { 149 1 : continue; 150 : } 151 : 152 1 : if (Objects.equals(g, root)) { 153 1 : problems.add( 154 1 : warning( 155 : "group %s (%s) contains a cycle: %s (%s) points to it as subgroup.", 156 1 : root.getName(), root.getGroupUUID(), t.getName(), t.getGroupUUID())); 157 : } 158 : 159 1 : todo.add(g); 160 1 : } 161 1 : } 162 1 : return problems; 163 : } 164 : }