Merge changes Icc3077b4,I33b4bc5d,I824caa34

* changes:
  AbstractDaemonTest: Rename AccountCreator variable to 'accountCreator'
  ExternalIdCacheImpl: Cache external IDs by email
  Add basic consistency checker for accounts
This commit is contained in:
David Pursehouse
2017-06-10 01:29:21 +00:00
committed by Gerrit Code Review
21 changed files with 236 additions and 68 deletions

View File

@@ -156,6 +156,7 @@ link:#consistency-check-input[ConsistencyCheckInput] entity.
Content-Type: application/json; charset=UTF-8
{
"check_accounts": {},
"check_account_external_ids": {}
}
----
@@ -170,6 +171,14 @@ is returned that contains detected consistency problems.
)]}'
{
"check_accounts_result": {
"problems": [
{
"status": "ERROR",
"message": "Account \u00271000024\u0027 has no external ID for its preferred email \u0027foo.bar@example.com\u0027"
}
]
}
"check_account_external_ids_result": {
"problems": [
{
@@ -1505,6 +1514,9 @@ consistency checks.
[options="header",cols="1,^1,5"]
|================================================
|Field Name ||Description
|`check_accounts_result` |optional|
The result of running the account consistency check as a
link:#check-accounts-result-info[CheckAccountsResultInfo] entity.
|`check_account_external_ids_result`|optional|
The result of running the account external ID consistency check as a
link:#check-account-external-ids-result-info[
@@ -1519,6 +1531,9 @@ consistency checks should be run.
[options="header",cols="1,^1,5"]
|=========================================
|Field Name ||Description
|`check_accounts` |optional|
Input for the account consistency check as
link:#check-accounts-input[CheckAccountsInput] entity.
|`check_account_external_ids`|optional|
Input for the account external ID consistency check as
link:#check-account-external-ids-input[CheckAccountExternalIdsInput]

View File

@@ -199,7 +199,7 @@ public abstract class AbstractDaemonTest {
@Inject @GerritServerConfig protected Config cfg;
@Inject protected AcceptanceTestRequestScope atrScope;
@Inject protected AccountCache accountCache;
@Inject protected AccountCreator accounts;
@Inject protected AccountCreator accountCreator;
@Inject protected AllProjectsName allProjects;
@Inject protected BatchUpdate.Factory batchUpdateFactory;
@Inject protected ChangeData.Factory changeDataFactory;
@@ -327,8 +327,8 @@ public abstract class AbstractDaemonTest {
server.getTestInjector().injectMembers(this);
Transport.register(inProcessProtocol);
toClose = Collections.synchronizedList(new ArrayList<Repository>());
admin = accounts.admin();
user = accounts.user();
admin = accountCreator.admin();
user = accountCreator.user();
// Evict cached user state in case tests modify it.
accountCache.evict(admin.getId());
@@ -370,7 +370,7 @@ public abstract class AbstractDaemonTest {
private TestAccount getCloneAsAccount(Description description) {
TestProjectInput ann = description.getAnnotation(TestProjectInput.class);
return accounts.get(ann != null ? ann.cloneAs() : "admin");
return accountCreator.get(ann != null ? ann.cloneAs() : "admin");
}
private ProjectInput projectInput(Description description) {

View File

@@ -52,6 +52,10 @@ import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccountsInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
@@ -90,6 +94,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -207,7 +212,7 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void create() throws Exception {
TestAccount foo = accounts.create("foo");
TestAccount foo = accountCreator.create("foo");
AccountInfo info = gApi.accounts().id(foo.id.get()).get();
assertThat(info.username).isEqualTo("foo");
if (SshMode.useSsh()) {
@@ -386,7 +391,7 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void ignoreChange() throws Exception {
TestAccount user2 = accounts.user2();
TestAccount user2 = accountCreator.user2();
accountIndexedCounter.clear();
PushOneCommit.Result r = createChange();
@@ -495,7 +500,7 @@ public class AccountIT extends AbstractDaemonTest {
@Test
public void cannotAddNonConfirmedEmailWithoutModifyAccountPermission() throws Exception {
TestAccount account = accounts.create(name("user"));
TestAccount account = accountCreator.create(name("user"));
EmailInput input = new EmailInput();
input.email = "test@test.com";
input.noConfirmation = true;
@@ -1047,6 +1052,41 @@ public class AccountIT extends AbstractDaemonTest {
gApi.accounts().id(admin.username).index();
}
@Test
@Sandboxed
public void checkConsistency() throws Exception {
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
resetCurrentApiUser();
// Create an account with a preferred email.
String username = name("foo");
String email = username + "@example.com";
TestAccount account = accountCreator.create(username, email, "Foo Bar");
ConsistencyCheckInput input = new ConsistencyCheckInput();
input.checkAccounts = new CheckAccountsInput();
ConsistencyCheckInfo checkInfo = gApi.config().server().checkConsistency(input);
assertThat(checkInfo.checkAccountsResult.problems).isEmpty();
Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
// Delete the external ID for the preferred email. This makes the account inconsistent since it
// now doesn't have an external ID for its preferred email.
externalIdsUpdate.delete(ExternalId.createEmail(account.getId(), email));
expectedProblems.add(
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.ERROR,
"Account '"
+ account.getId().get()
+ "' has no external ID for its preferred email '"
+ email
+ "'"));
checkInfo = gApi.config().server().checkConsistency(input);
assertThat(checkInfo.checkAccountsResult.problems).hasSize(expectedProblems.size());
assertThat(checkInfo.checkAccountsResult.problems).containsExactlyElementsIn(expectedProblems);
}
private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) {
int seq = 1;
for (SshKeyInfo key : sshKeys) {

View File

@@ -52,7 +52,7 @@ public class GeneralPreferencesIT extends AbstractDaemonTest {
@Before
public void setUp() throws Exception {
String name = name("user42");
user42 = accounts.create(name, name + "@example.com", "User 42");
user42 = accountCreator.create(name, name + "@example.com", "User 42");
}
@After

View File

@@ -453,16 +453,16 @@ public class ChangeIT extends AbstractDaemonTest {
ReviewInput in = ReviewInput.approve();
in.reviewer(user.email);
in.reviewer(accounts.user2().email, ReviewerState.CC, true);
in.reviewer(accountCreator.user2().email, ReviewerState.CC, true);
// Add user as reviewer that will create the revert
in.reviewer(accounts.admin2().email);
in.reviewer(accountCreator.admin2().email);
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
// expect both the original reviewers and CCs to be preserved
// original owner should be added as reviewer, user requesting the revert (new owner) removed
setApiUser(accounts.admin2());
setApiUser(accountCreator.admin2());
Map<ReviewerState, Collection<AccountInfo>> result =
gApi.changes().id(r.getChangeId()).revert().get().reviewers;
assertThat(result).containsKey(ReviewerState.REVIEWER);
@@ -473,11 +473,11 @@ public class ChangeIT extends AbstractDaemonTest {
assertThat(result).containsKey(ReviewerState.CC);
List<Integer> ccs =
result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
assertThat(ccs).containsExactly(accounts.user2().id.get());
assertThat(ccs).containsExactly(accountCreator.user2().id.get());
assertThat(reviewers).containsExactly(user.id.get(), admin.id.get());
} else {
assertThat(reviewers)
.containsExactly(user.id.get(), admin.id.get(), accounts.user2().id.get());
.containsExactly(user.id.get(), admin.id.get(), accountCreator.user2().id.get());
}
}
@@ -1633,7 +1633,7 @@ public class ChangeIT extends AbstractDaemonTest {
in.notify = NotifyHandling.NONE;
// notify unrelated account as TO
TestAccount user2 = accounts.user2();
TestAccount user2 = accountCreator.user2();
setApiUser(user);
recommend(r.getChangeId());
setApiUser(admin);

View File

@@ -92,8 +92,8 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void addMultipleMembers() throws Exception {
String g = createGroup("users");
TestAccount u1 = accounts.create("u1", "u1@example.com", "Full Name 1");
TestAccount u2 = accounts.create("u2", "u2@example.com", "Full Name 2");
TestAccount u1 = accountCreator.create("u1", "u1@example.com", "Full Name 1");
TestAccount u2 = accountCreator.create("u2", "u2@example.com", "Full Name 2");
gApi.groups().id(g).addMembers(u1.username, u2.username);
assertMembers(g, u1, u2);
}
@@ -101,10 +101,10 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void addMembersWithAtSign() throws Exception {
String g = createGroup("users");
TestAccount u10 = accounts.create("u10", "u10@example.com", "Full Name 10");
TestAccount u10 = accountCreator.create("u10", "u10@example.com", "Full Name 10");
TestAccount u11_at =
accounts.create("u11@something", "u11@example.com", "Full Name 11 With At");
accounts.create("u11", "u11.another@example.com", "Full Name 11 Without At");
accountCreator.create("u11@something", "u11@example.com", "Full Name 11 With At");
accountCreator.create("u11", "u11.another@example.com", "Full Name 11 Without At");
gApi.groups().id(g).addMembers(u10.username, u11_at.username);
assertMembers(g, u10, u11_at);
}
@@ -529,7 +529,7 @@ public class GroupsIT extends AbstractDaemonTest {
// reindex is tested by {@link AbstractQueryGroupsTest#reindex}
@Test
public void reindexPermissions() throws Exception {
TestAccount groupOwner = accounts.user2();
TestAccount groupOwner = accountCreator.user2();
GroupInput in = new GroupInput();
in.name = name("group");
in.members =
@@ -611,7 +611,7 @@ public class GroupsIT extends AbstractDaemonTest {
private String createAccount(String name, String group) throws Exception {
name = name(name);
accounts.create(name, group);
accountCreator.create(name, group);
return name;
}
}

View File

@@ -666,7 +666,7 @@ public class RevisionIT extends AbstractDaemonTest {
assertThat(sender.getMessages()).hasSize(0);
// Disable the notification. The user provided in the 'notifyDetails' should still be notified.
TestAccount userToNotify = accounts.user2();
TestAccount userToNotify = accountCreator.user2();
input.destination = "branch-3";
input.notify = NotifyHandling.NONE;
input.notifyDetails =

View File

@@ -247,7 +247,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
@Test
public void pushForMasterWithNotify() throws Exception {
// create a user that watches the project
TestAccount user3 = accounts.create("user3", "user3@example.com", "User3");
TestAccount user3 = accountCreator.create("user3", "user3@example.com", "User3");
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = project.get();
@@ -257,7 +257,7 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
setApiUser(user3);
gApi.accounts().self().setWatchedProjects(projectsToWatch);
TestAccount user2 = accounts.user2();
TestAccount user2 = accountCreator.user2();
String pushSpec = "refs/for/master%reviewer=" + user.email + ",cc=" + user2.email;
sender.clear();
@@ -335,11 +335,14 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
+ ",cc="
+ user.email
+ ",cc="
+ accounts.user2().email);
+ accountCreator.user2().email);
r.assertOkStatus();
// Check that admin isn't CC'd as they own the change
r.assertChange(
Change.Status.NEW, topic, ImmutableList.of(), ImmutableList.of(user, accounts.user2()));
Change.Status.NEW,
topic,
ImmutableList.of(),
ImmutableList.of(user, accountCreator.user2()));
// cc non-existing user
String nonExistingEmail = "non.existing@example.com";
@@ -365,7 +368,8 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
r.assertChange(Change.Status.NEW, topic, user);
// add several reviewers
TestAccount user2 = accounts.create("another-user", "another.user@example.com", "Another User");
TestAccount user2 =
accountCreator.create("another-user", "another.user@example.com", "Another User");
r =
pushTo(
"refs/for/master/"

View File

@@ -48,7 +48,7 @@ public class CheckAccessIT extends AbstractDaemonTest {
secretRefProject = createProject("secretRef");
privilegedGroup = groupCache.get(new AccountGroup.NameKey(createGroup("privilegedGroup")));
privilegedUser = accounts.create("privilegedUser", "snowden@nsa.gov", "Ed Snowden");
privilegedUser = accountCreator.create("privilegedUser", "snowden@nsa.gov", "Ed Snowden");
gApi.groups().id(privilegedGroup.getGroupUUID().get()).addMembers(privilegedUser.username);
assertThat(gApi.groups().id(privilegedGroup.getGroupUUID().get()).members().get(0).email)

View File

@@ -87,7 +87,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
@Before
public void setUp() throws Exception {
anonRestSession = new RestSession(server, null);
admin2 = accounts.admin2();
admin2 = accountCreator.admin2();
GroupInput gi = new GroupInput();
gi.name = name("New-Group");
gi.members = ImmutableList.of(user.id.toString());
@@ -322,7 +322,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
@Test
public void voteOnBehalfOfInvisibleUserNotAllowed() throws Exception {
allowCodeReviewOnBehalfOf();
setApiUser(accounts.user2());
setApiUser(accountCreator.user2());
assertThat(accountControlFactory.get().canSee(user.id)).isFalse();
PushOneCommit.Result r = createChange();
@@ -401,7 +401,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
@Test
public void submitOnBehalfOfInvisibleUserNotAllowed() throws Exception {
allowSubmitOnBehalfOf();
setApiUser(accounts.user2());
setApiUser(accountCreator.user2());
assertThat(accountControlFactory.get().canSee(user.id)).isFalse();
PushOneCommit.Result r = createChange();
@@ -502,7 +502,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
// X-Gerrit-RunAs user (user2).
allowRunAs();
allowCodeReviewOnBehalfOf();
TestAccount user2 = accounts.user2();
TestAccount user2 = accountCreator.user2();
PushOneCommit.Result r = createChange();
ReviewInput in = new ReviewInput();
@@ -542,7 +542,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
in.message = "Message on behalf of";
in.label("Code-Review", 1);
setApiUser(accounts.user2());
setApiUser(accountCreator.user2());
gApi.changes().id(r.getChangeId()).revision(r.getPatchSetId().getId()).review(in);
ChangeInfo info =
@@ -551,7 +551,7 @@ public class ImpersonationIT extends AbstractDaemonTest {
ChangeMessageInfo changeMessageInfo = Iterables.getLast(info.messages);
assertThat(changeMessageInfo.realAuthor).isNotNull();
assertThat(changeMessageInfo.realAuthor._accountId).isEqualTo(accounts.user2().id.get());
assertThat(changeMessageInfo.realAuthor._accountId).isEqualTo(accountCreator.user2().id.get());
}
private void allowCodeReviewOnBehalfOf() throws Exception {

View File

@@ -27,7 +27,7 @@ public class PutUsernameIT extends AbstractDaemonTest {
PutUsername.Input in = new PutUsername.Input();
in.username = "myUsername";
RestResponse r =
adminRestSession.put("/accounts/" + accounts.create().id.get() + "/username", in);
adminRestSession.put("/accounts/" + accountCreator.create().id.get() + "/username", in);
r.assertOK();
assertThat(newGson().fromJson(r.getReader(), String.class)).isEqualTo(in.username);
}
@@ -37,7 +37,7 @@ public class PutUsernameIT extends AbstractDaemonTest {
PutUsername.Input in = new PutUsername.Input();
in.username = admin.username;
adminRestSession
.put("/accounts/" + accounts.create().id.get() + "/username", in)
.put("/accounts/" + accountCreator.create().id.get() + "/username", in)
.assertConflict();
}

View File

@@ -39,7 +39,7 @@ public class ChangeOwnerIT extends AbstractDaemonTest {
@Before
public void setUp() throws Exception {
setApiUser(user);
user2 = accounts.user2();
user2 = accountCreator.user2();
}
@Test

View File

@@ -199,7 +199,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
// CC a group that overlaps with some existing reviewers and CCed accounts.
TestAccount reviewer =
accounts.create(name("reviewer"), "addCcGroup-reviewer@example.com", "Reviewer");
accountCreator.create(name("reviewer"), "addCcGroup-reviewer@example.com", "Reviewer");
result = addReviewer(changeId, reviewer.username);
assertThat(result.error).isNull();
sender.clear();
@@ -425,7 +425,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
@Test
public void reviewAndAddReviewers() throws Exception {
TestAccount observer = accounts.user2();
TestAccount observer = accountCreator.user2();
PushOneCommit.Result r = createChange();
ReviewInput input =
ReviewInput.approve().reviewer(user.email).reviewer(observer.email, CC, false);
@@ -480,7 +480,7 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
.id(mediumGroup)
.addMembers(usernames.subList(0, mediumGroupSize).toArray(new String[mediumGroupSize]));
TestAccount observer = accounts.user2();
TestAccount observer = accountCreator.user2();
PushOneCommit.Result r = createChange();
// Attempt to add overly large group as reviewers.
@@ -610,9 +610,12 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
@Test
public void addOverlappingGroups() throws Exception {
String emailPrefix = "addOverlappingGroups-";
TestAccount user1 = accounts.create(name("user1"), emailPrefix + "user1@example.com", "User1");
TestAccount user2 = accounts.create(name("user2"), emailPrefix + "user2@example.com", "User2");
TestAccount user3 = accounts.create(name("user3"), emailPrefix + "user3@example.com", "User3");
TestAccount user1 =
accountCreator.create(name("user1"), emailPrefix + "user1@example.com", "User1");
TestAccount user2 =
accountCreator.create(name("user2"), emailPrefix + "user2@example.com", "User2");
TestAccount user3 =
accountCreator.create(name("user3"), emailPrefix + "user3@example.com", "User3");
String group1 = createGroup("group1");
String group2 = createGroup("group2");
gApi.groups().id(group1).addMembers(user1.username, user2.username);
@@ -796,7 +799,8 @@ public class ChangeReviewersIT extends AbstractDaemonTest {
List<TestAccount> result = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
result.add(
accounts.create(name("u" + i), emailPrefix + "-" + i + "@example.com", "Full Name " + i));
accountCreator.create(
name("u" + i), emailPrefix + "-" + i + "@example.com", "Full Name " + i));
}
return result;
}

View File

@@ -421,7 +421,8 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
private TestAccount user(String name, String fullName, String emailName, AccountGroup... groups)
throws Exception {
String[] groupNames = Arrays.stream(groups).map(AccountGroup::getName).toArray(String[]::new);
return accounts.create(name(name), name(emailName) + "@example.com", fullName, groupNames);
return accountCreator.create(
name(name), name(emailName) + "@example.com", fullName, groupNames);
}
private TestAccount user(String name, String fullName, AccountGroup... groups) throws Exception {

View File

@@ -118,7 +118,7 @@ public class ConsistencyCheckerIT extends AbstractDaemonTest {
@Test
public void missingOwner() throws Exception {
TestAccount owner = accounts.create("missing");
TestAccount owner = accountCreator.create("missing");
ChangeControl ctl = insertChange(owner);
accountsUpdate.create().deleteByKey(db, owner.getId());

View File

@@ -350,7 +350,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
sender.clear();
// watch project as user2
TestAccount user2 = accounts.create("user2", "user2@test.com", "User2");
TestAccount user2 = accountCreator.create("user2", "user2@test.com", "User2");
setApiUser(user2);
watch(watchedProject, null);
@@ -467,7 +467,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
sender.clear();
// watch project as user2
TestAccount user2 = accounts.create("user2", "user2@test.com", "User2");
TestAccount user2 = accountCreator.create("user2", "user2@test.com", "User2");
setApiUser(user2);
watch(anyProject, null);
@@ -568,7 +568,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// watch project as user that can view all drafts
TestAccount userThatCanViewDrafts =
accounts.create("user2", "user2@test.com", "User2", groupThatCanViewDrafts.name);
accountCreator.create("user2", "user2@test.com", "User2", groupThatCanViewDrafts.name);
setApiUser(userThatCanViewDrafts);
watch(watchedProject, null);
@@ -639,7 +639,7 @@ public class ProjectWatchIT extends AbstractDaemonTest {
@Test
public void deleteAllProjectWatchesIfWatchConfigIsTheOnlyFileInUserBranch() throws Exception {
// Create account that has no files in its refs/users/ branch.
Account.Id id = accounts.create().id;
Account.Id id = accountCreator.create().id;
// Add a project watch so that a watch.config file in the refs/users/ branch is created.
Map<ProjectWatchKey, Set<NotifyType>> watches = new HashMap<>();
@@ -694,7 +694,8 @@ public class ProjectWatchIT extends AbstractDaemonTest {
// watch project as user that can view all private change
TestAccount userThatCanViewPrivateChanges =
accounts.create("user2", "user2@test.com", "User2", groupThatCanViewPrivateChanges.name);
accountCreator.create(
"user2", "user2@test.com", "User2", groupThatCanViewPrivateChanges.name);
setApiUser(userThatCanViewPrivateChanges);
watch(watchedProject, null);

View File

@@ -18,8 +18,17 @@ import java.util.List;
import java.util.Objects;
public class ConsistencyCheckInfo {
public CheckAccountsResultInfo checkAccountsResult;
public CheckAccountExternalIdsResultInfo checkAccountExternalIdsResult;
public static class CheckAccountsResultInfo {
public List<ConsistencyProblemInfo> problems;
public CheckAccountsResultInfo(List<ConsistencyProblemInfo> problems) {
this.problems = problems;
}
}
public static class CheckAccountExternalIdsResultInfo {
public List<ConsistencyProblemInfo> problems;

View File

@@ -15,7 +15,10 @@
package com.google.gerrit.extensions.api.config;
public class ConsistencyCheckInput {
public CheckAccountsInput checkAccounts;
public CheckAccountExternalIdsInput checkAccountExternalIds;
public static class CheckAccountsInput {}
public static class CheckAccountExternalIdsInput {}
}

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Singleton
public class AccountsConsistencyChecker {
private final Provider<ReviewDb> dbProvider;
private final ExternalIds externalIds;
@Inject
AccountsConsistencyChecker(Provider<ReviewDb> dbProvider, ExternalIds externalIds) {
this.dbProvider = dbProvider;
this.externalIds = externalIds;
}
public List<ConsistencyProblemInfo> check() throws OrmException, IOException {
List<ConsistencyProblemInfo> problems = new ArrayList<>();
for (Account account : dbProvider.get().accounts().all()) {
if (account.getPreferredEmail() != null) {
if (!externalIds
.byAccount(account.getId())
.stream()
.anyMatch(e -> account.getPreferredEmail().equals(e.email()))) {
addError(
String.format(
"Account '%s' has no external ID for its preferred email '%s'",
account.getId().get(), account.getPreferredEmail()),
problems);
}
}
}
return problems;
}
private static void addError(String error, List<ConsistencyProblemInfo> problems) {
problems.add(new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, error));
}
}

View File

@@ -14,8 +14,11 @@
package com.google.gerrit.server.account.externalids;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static java.util.stream.Collectors.toSet;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
@@ -44,8 +47,7 @@ import org.slf4j.LoggerFactory;
class ExternalIdCacheImpl implements ExternalIdCache {
private static final Logger log = LoggerFactory.getLogger(ExternalIdCacheImpl.class);
private final LoadingCache<ObjectId, ImmutableSetMultimap<Account.Id, ExternalId>>
extIdsByAccount;
private final LoadingCache<ObjectId, AllExternalIds> extIdsByAccount;
private final ExternalIdReader externalIdReader;
private final Lock lock;
@@ -217,7 +219,7 @@ class ExternalIdCacheImpl implements ExternalIdCache {
@Override
public Set<ExternalId> byAccount(Account.Id accountId) throws IOException {
try {
return extIdsByAccount.get(externalIdReader.readRevision()).get(accountId);
return extIdsByAccount.get(externalIdReader.readRevision()).byAccount().get(accountId);
} catch (ExecutionException e) {
throw new IOException("Cannot list external ids by account", e);
}
@@ -226,12 +228,7 @@ class ExternalIdCacheImpl implements ExternalIdCache {
@Override
public Set<ExternalId> byEmail(String email) throws IOException {
try {
return extIdsByAccount
.get(externalIdReader.readRevision())
.values()
.stream()
.filter(e -> email.equals(e.email()))
.collect(toSet());
return extIdsByAccount.get(externalIdReader.readRevision()).byEmail().get(email);
} catch (ExecutionException e) {
throw new IOException("Cannot list external ids by email", e);
}
@@ -245,12 +242,15 @@ class ExternalIdCacheImpl implements ExternalIdCache {
try {
ListMultimap<Account.Id, ExternalId> m;
if (!ObjectId.zeroId().equals(oldNotesRev)) {
m = MultimapBuilder.hashKeys().arrayListValues().build(extIdsByAccount.get(oldNotesRev));
m =
MultimapBuilder.hashKeys()
.arrayListValues()
.build(extIdsByAccount.get(oldNotesRev).byAccount());
} else {
m = MultimapBuilder.hashKeys().arrayListValues().build();
}
update.accept(m);
extIdsByAccount.put(newNotesRev, ImmutableSetMultimap.copyOf(m));
extIdsByAccount.put(newNotesRev, AllExternalIds.create(m));
} catch (ExecutionException e) {
log.warn("Cannot update external IDs", e);
} finally {
@@ -262,8 +262,7 @@ class ExternalIdCacheImpl implements ExternalIdCache {
Collections2.transform(ids, e -> e.key()).removeAll(toRemove);
}
private static class Loader
extends CacheLoader<ObjectId, ImmutableSetMultimap<Account.Id, ExternalId>> {
private static class Loader extends CacheLoader<ObjectId, AllExternalIds> {
private final ExternalIdReader externalIdReader;
Loader(ExternalIdReader externalIdReader) {
@@ -271,13 +270,31 @@ class ExternalIdCacheImpl implements ExternalIdCache {
}
@Override
public ImmutableSetMultimap<Account.Id, ExternalId> load(ObjectId notesRev) throws Exception {
public AllExternalIds load(ObjectId notesRev) throws Exception {
Multimap<Account.Id, ExternalId> extIdsByAccount =
MultimapBuilder.hashKeys().arrayListValues().build();
for (ExternalId extId : externalIdReader.all(notesRev)) {
extIdsByAccount.put(extId.accountId(), extId);
}
return ImmutableSetMultimap.copyOf(extIdsByAccount);
return AllExternalIds.create(extIdsByAccount);
}
}
@AutoValue
abstract static class AllExternalIds {
static AllExternalIds create(Multimap<Account.Id, ExternalId> byAccount) {
ImmutableSetMultimap<String, ExternalId> byEmail =
byAccount
.values()
.stream()
.filter(e -> !Strings.isNullOrEmpty(e.email()))
.collect(toImmutableSetMultimap(ExternalId::email, e -> e));
return new AutoValue_ExternalIdCacheImpl_AllExternalIds(
ImmutableSetMultimap.copyOf(byAccount), byEmail);
}
public abstract ImmutableSetMultimap<Account.Id, ExternalId> byAccount();
public abstract ImmutableSetMultimap<String, ExternalId> byEmail();
}
}

View File

@@ -16,13 +16,16 @@ package com.google.gerrit.server.config;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckAccountExternalIdsResultInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckAccountsResultInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountsConsistencyChecker;
import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -31,19 +34,22 @@ import java.io.IOException;
@Singleton
public class CheckConsistency implements RestModifyView<ConfigResource, ConsistencyCheckInput> {
private final Provider<IdentifiedUser> userProvider;
private final AccountsConsistencyChecker accountsConsistencyChecker;
private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
@Inject
CheckConsistency(
Provider<IdentifiedUser> currentUser,
AccountsConsistencyChecker accountsConsistencyChecker,
ExternalIdsConsistencyChecker externalIdsConsistencyChecker) {
this.userProvider = currentUser;
this.accountsConsistencyChecker = accountsConsistencyChecker;
this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
}
@Override
public ConsistencyCheckInfo apply(ConfigResource resource, ConsistencyCheckInput input)
throws RestApiException, IOException {
throws RestApiException, IOException, OrmException {
IdentifiedUser user = userProvider.get();
if (!user.isIdentifiedUser()) {
throw new AuthException("Authentication required");
@@ -52,11 +58,15 @@ public class CheckConsistency implements RestModifyView<ConfigResource, Consiste
throw new AuthException("not allowed to run consistency checks");
}
if (input == null || input.checkAccountExternalIds == null) {
if (input == null || (input.checkAccounts == null && input.checkAccountExternalIds == null)) {
throw new BadRequestException("input required");
}
ConsistencyCheckInfo consistencyCheckInfo = new ConsistencyCheckInfo();
if (input.checkAccounts != null) {
consistencyCheckInfo.checkAccountsResult =
new CheckAccountsResultInfo(accountsConsistencyChecker.check());
}
if (input.checkAccountExternalIds != null) {
consistencyCheckInfo.checkAccountExternalIdsResult =
new CheckAccountExternalIdsResultInfo(externalIdsConsistencyChecker.check());