blob: 05f6796d405f34834b44a837f16831d4c100f94b [file] [log] [blame]
// Copyright (C) 2016 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.query.account;
import static com.google.common.truth.Truth.assertThat;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.accounts.Accounts.QueryRequest;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.ListAccountsOption;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountConfig;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.GerritServerTests;
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.util.Providers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public abstract class AbstractQueryAccountsTest extends GerritServerTests {
@ConfigSuite.Default
public static Config defaultConfig() {
Config cfg = new Config();
cfg.setInt("index", null, "maxPages", 10);
return cfg;
}
@Inject protected Accounts accounts;
@Inject protected AccountsUpdate.Server accountsUpdate;
@Inject protected AccountCache accountCache;
@Inject protected AccountManager accountManager;
@Inject protected GerritApi gApi;
@Inject @GerritPersonIdent Provider<PersonIdent> serverIdent;
@Inject protected IdentifiedUser.GenericFactory userFactory;
@Inject private Provider<AnonymousUser> anonymousUser;
@Inject protected InMemoryDatabase schemaFactory;
@Inject protected SchemaCreator schemaCreator;
@Inject protected ThreadLocalRequestContext requestContext;
@Inject protected OneOffRequestContext oneOffRequestContext;
@Inject protected Provider<InternalAccountQuery> queryProvider;
@Inject protected AllProjectsName allProjects;
@Inject protected AllUsersName allUsers;
@Inject protected GitRepositoryManager repoManager;
@Inject protected AccountIndexCollection accountIndexes;
protected LifecycleManager lifecycle;
protected Injector injector;
protected ReviewDb db;
protected AccountInfo currentUserInfo;
protected CurrentUser user;
protected abstract Injector createInjector();
@Before
public void setUpInjector() throws Exception {
lifecycle = new LifecycleManager();
injector = createInjector();
lifecycle.add(injector);
injector.injectMembers(this);
lifecycle.start();
initAfterLifecycleStart();
setUpDatabase();
}
@After
public void cleanUp() {
lifecycle.stop();
db.close();
}
protected void setUpDatabase() throws Exception {
db = schemaFactory.open();
schemaCreator.create(db);
Account.Id userId = createAccount("user", "User", "user@example.com", true);
user = userFactory.create(userId);
requestContext.setContext(newRequestContext(userId));
currentUserInfo = gApi.accounts().id(userId.get()).get();
}
protected void initAfterLifecycleStart() throws Exception {}
protected RequestContext newRequestContext(Account.Id requestUserId) {
final CurrentUser requestUser = userFactory.create(requestUserId);
return new RequestContext() {
@Override
public CurrentUser getUser() {
return requestUser;
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return Providers.of(db);
}
};
}
protected void setAnonymous() {
requestContext.setContext(
new RequestContext() {
@Override
public CurrentUser getUser() {
return anonymousUser.get();
}
@Override
public Provider<ReviewDb> getReviewDbProvider() {
return Providers.of(db);
}
});
}
@After
public void tearDownInjector() {
if (lifecycle != null) {
lifecycle.stop();
}
requestContext.setContext(null);
if (db != null) {
db.close();
}
InMemoryDatabase.drop(schemaFactory);
}
@Test
public void byId() throws Exception {
AccountInfo user = newAccount("user");
assertQuery("9999999");
assertQuery(currentUserInfo._accountId, currentUserInfo);
assertQuery(user._accountId, user);
}
@Test
public void bySelf() throws Exception {
assertQuery("self", currentUserInfo);
}
@Test
public void byEmail() throws Exception {
AccountInfo user1 = newAccountWithEmail("user1", name("user1@example.com"));
String domain = name("test.com");
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
String prefix = name("prefix");
AccountInfo user4 = newAccountWithEmail("user4", prefix + "user4@example.com");
AccountInfo user5 = newAccountWithEmail("user5", name("user5MixedCase@example.com"));
assertQuery("notexisting@test.com");
assertQuery(currentUserInfo.email, currentUserInfo);
assertQuery("email:" + currentUserInfo.email, currentUserInfo);
assertQuery(user1.email, user1);
assertQuery("email:" + user1.email, user1);
assertQuery(domain, user2, user3);
assertQuery("email:" + prefix, user4);
assertQuery(user5.email, user5);
assertQuery("email:" + user5.email, user5);
assertQuery("email:" + user5.email.toUpperCase(), user5);
}
@Test
public void byUsername() throws Exception {
AccountInfo user1 = newAccount("myuser");
assertQuery("notexisting");
assertQuery("Not Existing");
assertQuery(user1.username, user1);
assertQuery("username:" + user1.username, user1);
assertQuery("username:" + user1.username.toUpperCase(), user1);
}
@Test
public void isActive() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccount("user3", "user3@" + domain, false);
AccountInfo user4 = newAccount("user4", "user4@" + domain, false);
// by default only active accounts are returned
assertQuery(domain, user1, user2);
assertQuery("name:" + domain, user1, user2);
assertQuery("is:active name:" + domain, user1, user2);
assertQuery("is:inactive name:" + domain, user3, user4);
}
@Test
public void byName() throws Exception {
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
assertQuery("notexisting");
assertQuery("Not Existing");
assertQuery(quote(user1.name), user1);
assertQuery("name:" + quote(user1.name), user1);
assertQuery("John", user1);
assertQuery("john", user1);
assertQuery("Doe", user1);
assertQuery("doe", user1);
assertQuery("DOE", user1);
assertQuery("Jo Do", user1);
assertQuery("jo do", user1);
assertQuery("self", currentUserInfo, user3);
assertQuery("me", currentUserInfo);
assertQuery("name:John", user1);
assertQuery("name:john", user1);
assertQuery("name:Doe", user1);
assertQuery("name:doe", user1);
assertQuery("name:DOE", user1);
assertQuery("name:self", user3);
assertQuery(quote(user2.name), user2);
assertQuery("name:" + quote(user2.name), user2);
}
@Test
public void byCansee() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("account1", "account1@" + domain);
AccountInfo user2 = newAccountWithEmail("account2", "account2@" + domain);
AccountInfo user3 = newAccountWithEmail("account3", "account3@" + domain);
Project.NameKey p = createProject(name("p"));
ChangeInfo c = createChange(p);
assertQuery("name:" + domain + " cansee:" + c.changeId, user1, user2, user3);
GroupInfo group = createGroup(name("group"), user1, user2);
blockRead(p, group);
assertQuery("name:" + domain + " cansee:" + c.changeId, user3);
}
@Test
public void byWatchedProject() throws Exception {
Project.NameKey p = createProject(name("p"));
Project.NameKey p2 = createProject(name("p2"));
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
assertThat(queryProvider.get().byWatchedProject(p)).isEmpty();
watch(user1, p, null);
assertAccounts(queryProvider.get().byWatchedProject(p), user1);
watch(user2, p, "keyword");
assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
watch(user3, p2, "keyword");
watch(user3, allProjects, "keyword");
assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
assertAccounts(queryProvider.get().byWatchedProject(p2), user3);
assertAccounts(queryProvider.get().byWatchedProject(allProjects), user3);
}
@Test
public void byDeletedAccount() throws Exception {
AccountInfo user = newAccountWithFullName("jdoe", "John Doe");
Account.Id userId = Account.Id.parse(user._accountId.toString());
assertQuery("John", user);
for (AccountIndex index : accountIndexes.getWriteIndexes()) {
index.delete(userId);
}
assertQuery("John");
}
@Test
public void withLimit() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
List<AccountInfo> result = assertQuery(domain, user1, user2, user3);
assertThat(Iterables.getLast(result)._moreAccounts).isNull();
result = assertQuery(newQuery(domain).withLimit(2), result.subList(0, 2));
assertThat(Iterables.getLast(result)._moreAccounts).isTrue();
}
@Test
public void withStart() throws Exception {
String domain = name("test.com");
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
List<AccountInfo> result = assertQuery(domain, user1, user2, user3);
assertQuery(newQuery(domain).withStart(1), result.subList(1, 3));
}
@Test
public void withDetails() throws Exception {
AccountInfo user1 = newAccount("myuser", "My User", "my.user@example.com", true);
List<AccountInfo> result = assertQuery(user1.username, user1);
AccountInfo ai = result.get(0);
assertThat(ai._accountId).isEqualTo(user1._accountId);
assertThat(ai.name).isNull();
assertThat(ai.username).isNull();
assertThat(ai.email).isNull();
assertThat(ai.avatars).isNull();
result = assertQuery(newQuery(user1.username).withOption(ListAccountsOption.DETAILS), user1);
ai = result.get(0);
assertThat(ai._accountId).isEqualTo(user1._accountId);
assertThat(ai.name).isEqualTo(user1.name);
assertThat(ai.username).isEqualTo(user1.username);
assertThat(ai.email).isEqualTo(user1.email);
assertThat(ai.avatars).isNull();
}
@Test
public void withSecondaryEmails() throws Exception {
AccountInfo user1 = newAccount("myuser", "My User", "my.user@example.com", true);
String[] secondaryEmails = new String[] {"bar@example.com", "foo@example.com"};
addEmails(user1, secondaryEmails);
List<AccountInfo> result = assertQuery(user1.username, user1);
assertThat(result.get(0).secondaryEmails).isNull();
result = assertQuery(newQuery(user1.username).withOption(ListAccountsOption.DETAILS), user1);
assertThat(result.get(0).secondaryEmails).isNull();
result = assertQuery(newQuery(user1.username).withOption(ListAccountsOption.ALL_EMAILS), user1);
assertThat(result.get(0).secondaryEmails)
.containsExactlyElementsIn(Arrays.asList(secondaryEmails))
.inOrder();
result =
assertQuery(
newQuery(user1.username)
.withOptions(ListAccountsOption.DETAILS, ListAccountsOption.ALL_EMAILS),
user1);
assertThat(result.get(0).secondaryEmails)
.containsExactlyElementsIn(Arrays.asList(secondaryEmails))
.inOrder();
}
@Test
public void asAnonymous() throws Exception {
AccountInfo user1 = newAccount("user1");
setAnonymous();
assertQuery("9999999");
assertQuery("self");
assertQuery("username:" + user1.username, user1);
}
// reindex permissions are tested by {@link AccountIT#reindexPermissions}
@Test
public void reindex() throws Exception {
AccountInfo user1 = newAccountWithFullName("tester", "Test Usre");
// update account without reindex so that account index is stale
Account.Id accountId = new Account.Id(user1._accountId);
String newName = "Test User";
try (Repository repo = repoManager.openRepository(allUsers)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsers, repo);
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
AccountConfig accountConfig = new AccountConfig(null, accountId);
accountConfig.load(repo);
accountConfig.getAccount().setFullName(newName);
accountConfig.commit(md);
}
assertQuery("name:" + quote(user1.name), user1);
assertQuery("name:" + quote(newName));
gApi.accounts().id(user1.username).index();
assertQuery("name:" + quote(user1.name));
assertQuery("name:" + quote(newName), user1);
}
protected AccountInfo newAccount(String username) throws Exception {
return newAccountWithEmail(username, null);
}
protected AccountInfo newAccountWithEmail(String username, String email) throws Exception {
return newAccount(username, email, true);
}
protected AccountInfo newAccountWithFullName(String username, String fullName) throws Exception {
return newAccount(username, fullName, null, true);
}
protected AccountInfo newAccount(String username, String email, boolean active) throws Exception {
return newAccount(username, null, email, active);
}
protected AccountInfo newAccount(String username, String fullName, String email, boolean active)
throws Exception {
String uniqueName = name(username);
try {
gApi.accounts().id(uniqueName).get();
fail("user " + uniqueName + " already exists");
} catch (ResourceNotFoundException e) {
// expected: user does not exist yet
}
Account.Id id = createAccount(uniqueName, fullName, email, active);
return gApi.accounts().id(id.get()).get();
}
protected Project.NameKey createProject(String name) throws RestApiException {
ProjectInput in = new ProjectInput();
in.name = name;
in.createEmptyCommit = true;
gApi.projects().create(in);
return new Project.NameKey(name);
}
protected void blockRead(Project.NameKey project, GroupInfo group) throws RestApiException {
ProjectAccessInput in = new ProjectAccessInput();
in.add = new HashMap<>();
AccessSectionInfo a = new AccessSectionInfo();
PermissionInfo p = new PermissionInfo(null, null);
p.rules =
ImmutableMap.of(group.id, new PermissionRuleInfo(PermissionRuleInfo.Action.BLOCK, false));
a.permissions = ImmutableMap.of("read", p);
in.add = ImmutableMap.of("refs/*", a);
gApi.projects().name(project.get()).access(in);
}
protected ChangeInfo createChange(Project.NameKey project) throws RestApiException {
ChangeInput in = new ChangeInput();
in.subject = "A change";
in.project = project.get();
in.branch = "master";
return gApi.changes().create(in).get();
}
protected GroupInfo createGroup(String name, AccountInfo... members) throws RestApiException {
GroupInput in = new GroupInput();
in.name = name;
in.members =
Arrays.asList(members).stream().map(a -> String.valueOf(a._accountId)).collect(toList());
return gApi.groups().create(in).get();
}
protected void watch(AccountInfo account, Project.NameKey project, String filter)
throws RestApiException {
List<ProjectWatchInfo> projectsToWatch = new ArrayList<>();
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.project = project.get();
pwi.filter = filter;
pwi.notifyAbandonedChanges = true;
pwi.notifyNewChanges = true;
pwi.notifyAllComments = true;
projectsToWatch.add(pwi);
gApi.accounts().id(account._accountId).setWatchedProjects(projectsToWatch);
}
protected String quote(String s) {
return "\"" + s + "\"";
}
protected String name(String name) {
if (name == null) {
return null;
}
String suffix = getSanitizedMethodName();
if (name.contains("@")) {
return name + "." + suffix;
}
return name + "_" + suffix;
}
private Account.Id createAccount(String username, String fullName, String email, boolean active)
throws Exception {
try (ManualRequestContext ctx = oneOffRequestContext.open()) {
Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
if (email != null) {
accountManager.link(id, AuthRequest.forEmail(email));
}
accountsUpdate
.create()
.update(
id,
a -> {
a.setFullName(fullName);
a.setPreferredEmail(email);
a.setActive(active);
});
return id;
}
}
private void addEmails(AccountInfo account, String... emails) throws Exception {
Account.Id id = new Account.Id(account._accountId);
for (String email : emails) {
accountManager.link(id, AuthRequest.forEmail(email));
}
accountCache.evict(id);
}
protected QueryRequest newQuery(Object query) throws RestApiException {
return gApi.accounts().query(query.toString());
}
protected List<AccountInfo> assertQuery(Object query, AccountInfo... accounts) throws Exception {
return assertQuery(newQuery(query), accounts);
}
protected List<AccountInfo> assertQuery(QueryRequest query, AccountInfo... accounts)
throws Exception {
return assertQuery(query, Arrays.asList(accounts));
}
protected List<AccountInfo> assertQuery(QueryRequest query, List<AccountInfo> accounts)
throws Exception {
List<AccountInfo> result = query.get();
Iterable<Integer> ids = ids(result);
assertThat(ids)
.named(format(query, result, accounts))
.containsExactlyElementsIn(ids(accounts))
.inOrder();
return result;
}
protected void assertAccounts(List<AccountState> accounts, AccountInfo... expectedAccounts) {
assertThat(accounts.stream().map(a -> a.getAccount().getId().get()).collect(toList()))
.containsExactlyElementsIn(
Arrays.asList(expectedAccounts).stream().map(a -> a._accountId).collect(toList()));
}
private String format(
QueryRequest query, List<AccountInfo> actualIds, List<AccountInfo> expectedAccounts) {
StringBuilder b = new StringBuilder();
b.append("query '").append(query.getQuery()).append("' with expected accounts ");
b.append(format(expectedAccounts));
b.append(" and result ");
b.append(format(actualIds));
return b.toString();
}
private String format(Iterable<AccountInfo> accounts) {
StringBuilder b = new StringBuilder();
b.append("[");
Iterator<AccountInfo> it = accounts.iterator();
while (it.hasNext()) {
AccountInfo a = it.next();
b.append("{")
.append(a._accountId)
.append(", ")
.append("name=")
.append(a.name)
.append(", ")
.append("email=")
.append(a.email)
.append(", ")
.append("username=")
.append(a.username)
.append("}");
if (it.hasNext()) {
b.append(", ");
}
}
b.append("]");
return b.toString();
}
protected static Iterable<Integer> ids(AccountInfo... accounts) {
return ids(Arrays.asList(accounts));
}
protected static Iterable<Integer> ids(List<AccountInfo> accounts) {
return accounts.stream().map(a -> a._accountId).collect(toList());
}
}