Fix: Require proper permission to search users by secondary email
We currently allow probing for secondary emails e.g. via
/accounts/secondary_email/detail. But reading secondary emails should
actually require MODIFY_ACCOUNT permission.
Change-Id: Ic0f36127a3d8134371fe11baf7a8cb57f8e369f1
diff --git a/java/com/google/gerrit/server/account/AccountResolver.java b/java/com/google/gerrit/server/account/AccountResolver.java
index 103013c..68f5a85 100644
--- a/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/java/com/google/gerrit/server/account/AccountResolver.java
@@ -27,12 +27,16 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.gerrit.entities.Account;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.index.Schema;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -437,7 +441,15 @@
// up with a reasonable result list.
// TODO(dborowitz): This doesn't match the documentation; consider whether it's possible to be
// more strict here.
- return accountQueryProvider.get().enforceVisibility(true).byDefault(input).stream();
+ boolean canSeeSecondaryEmails = false;
+ try {
+ permissionBackend.user(self.get()).check(GlobalPermission.MODIFY_ACCOUNT);
+ canSeeSecondaryEmails = true;
+ } catch (AuthException | PermissionBackendException e) {
+ // remains false
+ }
+ return accountQueryProvider.get().enforceVisibility(true)
+ .byDefault(input, canSeeSecondaryEmails).stream();
}
@Override
@@ -473,6 +485,7 @@
private final Provider<InternalAccountQuery> accountQueryProvider;
private final Realm realm;
private final String anonymousCowardName;
+ private final PermissionBackend permissionBackend;
@Inject
AccountResolver(
@@ -482,15 +495,17 @@
IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self,
Provider<InternalAccountQuery> accountQueryProvider,
+ PermissionBackend permissionBackend,
Realm realm,
@AnonymousCowardName String anonymousCowardName) {
- this.realm = realm;
this.accountCache = accountCache;
+ this.emails = emails;
this.accountControlFactory = accountControlFactory;
this.userFactory = userFactory;
this.self = self;
this.accountQueryProvider = accountQueryProvider;
- this.emails = emails;
+ this.permissionBackend = permissionBackend;
+ this.realm = realm;
this.anonymousCowardName = anonymousCowardName;
}
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 091edca..b7f8b45 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -54,8 +54,8 @@
super(queryProcessor, indexes, indexConfig);
}
- public List<AccountState> byDefault(String query) {
- return query(AccountPredicates.defaultPredicate(schema(), true, query));
+ public List<AccountState> byDefault(String query, boolean canSeeSecondaryEmails) {
+ return query(AccountPredicates.defaultPredicate(schema(), canSeeSecondaryEmails, query));
}
public List<AccountState> byExternalId(String scheme, String id) {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index d54574a..8c7d892 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -1797,7 +1797,7 @@
}
assertThat(accountCache.get(admin.id())).isEmpty();
- assertThat(accountQueryProvider.get().byDefault(admin.id().toString())).isEmpty();
+ assertThat(accountQueryProvider.get().byDefault(admin.id().toString(), true)).isEmpty();
}
@Test
@@ -2187,7 +2187,7 @@
@Test
public void internalQueryFindActiveAndInactiveAccounts() throws Exception {
String name = name("foo");
- assertThat(accountQueryProvider.get().byDefault(name)).isEmpty();
+ assertThat(accountQueryProvider.get().byDefault(name, true)).isEmpty();
TestAccount foo1 = accountCreator.create(name + "-1");
assertThat(gApi.accounts().id(foo1.username()).getActive()).isTrue();
@@ -2196,7 +2196,7 @@
gApi.accounts().id(foo2.username()).setActive(false);
assertThat(gApi.accounts().id(foo2.id().get()).getActive()).isFalse();
- assertThat(accountQueryProvider.get().byDefault(name)).hasSize(2);
+ assertThat(accountQueryProvider.get().byDefault(name, true)).hasSize(2);
}
@Test
@@ -3014,6 +3014,20 @@
}
}
+ @Test
+ public void searchForSecondaryEmailRequiresModifyAccountPermission() throws Exception {
+ String email = "preferred@example.com";
+ TestAccount foo = accountCreator.create(name("foo"), email, "Foo", null);
+ String secondaryEmail = "secondary@example.com";
+ EmailInput input = newEmailInput(secondaryEmail);
+ gApi.accounts().id(foo.id().get()).addEmail(input);
+
+ requestScopeOperations.setApiUser(user.id());
+ assertThrows(ResourceNotFoundException.class, () -> gApi.accounts().id("secondary"));
+ requestScopeOperations.setApiUser(admin.id());
+ assertThat(gApi.accounts().id("secondary").get()._accountId).isEqualTo(foo.id().get());
+ }
+
private void createDraft(PushOneCommit.Result r, String path, String message) throws Exception {
DraftInput in = new DraftInput();
in.path = path;
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
index b999abd..84f95f7 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/GetAccountDetailIT.java
@@ -58,4 +58,21 @@
AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
assertThat(info.tags).containsExactly(AccountInfo.Tag.SERVICE_USER);
}
+
+ @Test
+ public void searchForSecondaryEmailRequiresModifyAccountPermission() throws Exception {
+ Account.Id id =
+ accountOperations
+ .newAccount()
+ .preferredEmail("preferred@email")
+ .addSecondaryEmail("secondary@email")
+ .create();
+ RestResponse r = userRestSession.get("/accounts/secondary/detail/");
+ r.assertStatus(404);
+ // The admin has MODIFY_ACCOUNT permission and can see the user.
+ r = adminRestSession.get("/accounts/secondary/detail/");
+ r.assertStatus(200);
+ AccountDetailInfo info = newGson().fromJson(r.getReader(), AccountDetailInfo.class);
+ assertThat(info._accountId).isEqualTo(id.get());
+ }
}
diff --git a/javatests/com/google/gerrit/server/account/AccountResolverTest.java b/javatests/com/google/gerrit/server/account/AccountResolverTest.java
index 5f14d28..5e49aaf 100644
--- a/javatests/com/google/gerrit/server/account/AccountResolverTest.java
+++ b/javatests/com/google/gerrit/server/account/AccountResolverTest.java
@@ -352,7 +352,7 @@
}
private static AccountResolver newAccountResolver() {
- return new AccountResolver(null, null, null, null, null, null, null, "Anonymous Name");
+ return new AccountResolver(null, null, null, null, null, null, null, null, "Anonymous Name");
}
private AccountState newAccount(int id) {