Account index: Add new field that contains name parts without secondary emails

In a follow-up change we want to hide secondary emails from users that
don't have the 'Modify Account' capability. For this we must also
disable querying accounts by secondary email. Otherwise one could search
for 'foo.com' to find all accounts that have a '*@foo.com' email
address.

Currently the NAME_PART field also contains the name parts of the
secondary email addresses. This means we must not use this field if the
calling user doesn't have the 'Modify Account' capability. However since
the NAME_PART field is needed for serving default queries and queries
with the 'name' operator we need a replacement for this field that
contains the name parts without secondary emails. This change adds this
field and create a new schema version.

This change is separate from the follow-up change that requires the
'Modify Account' capability to see secondary email addresses so that at
Google we can first upgrade the account index and only then rollout the
change that needs to new index field. The follow-up change also works
with old index versions, but can't support name queries by prefix
without the new field, which affect reviewer suggestion. Having this as
2 changes allows us to do the rollout without affecting users.

Change-Id: If0ac6daa87403d94025a31a37ddf0b118cd5b579
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin 2017-11-29 15:46:01 +01:00 committed by Han-Wen Nienhuys
parent cfa0e4334e
commit 96750907d6
2 changed files with 46 additions and 14 deletions

View File

@ -33,6 +33,7 @@ import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.index.RefState;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
@ -43,27 +44,38 @@ public class AccountField {
public static final FieldDef<AccountState, Integer> ID =
integer("id").stored().build(a -> a.getAccount().getId().get());
/**
* External IDs.
*
* <p>This field includes secondary emails. Use this field only if the current user is allowed to
* see secondary emails (requires the {@link GlobalCapability.MODIFY_ACCOUNT} capability).
*/
public static final FieldDef<AccountState, Iterable<String>> EXTERNAL_ID =
exact("external_id")
.buildRepeatable(a -> Iterables.transform(a.getExternalIds(), id -> id.key().get()));
/** Fuzzy prefix match on name and email parts. */
/**
* Fuzzy prefix match on name and email parts.
*
* <p>This field includes parts from the secondary emails. Use this field only if the current user
* is allowed to see secondary emails (requires the {@link GlobalCapability.MODIFY_ACCOUNT}
* capability).
*
* <p>Use the {@link AccountField#NAME_PART_NO_SECONDARY_EMAIL} if the current user can't see
* secondary emails.
*/
public static final FieldDef<AccountState, Iterable<String>> NAME_PART =
prefix("name")
.buildRepeatable(
a -> {
String fullName = a.getAccount().getFullName();
Set<String> parts =
SchemaUtil.getNameParts(
fullName, Iterables.transform(a.getExternalIds(), ExternalId::email));
a -> getNameParts(a, Iterables.transform(a.getExternalIds(), ExternalId::email)));
// Additional values not currently added by getPersonParts.
// TODO(dborowitz): Move to getPersonParts and remove this hack.
if (fullName != null) {
parts.add(fullName.toLowerCase(Locale.US));
}
return parts;
});
/**
* Fuzzy prefix match on name and preferred email parts. Parts of secondary emails are not
* included.
*/
public static final FieldDef<AccountState, Iterable<String>> NAME_PART_NO_SECONDARY_EMAIL =
prefix("name2")
.buildRepeatable(a -> getNameParts(a, Arrays.asList(a.getAccount().getPreferredEmail())));
public static final FieldDef<AccountState, String> FULL_NAME =
exact("full_name").build(a -> a.getAccount().getFullName());
@ -71,6 +83,12 @@ public class AccountField {
public static final FieldDef<AccountState, String> ACTIVE =
exact("inactive").build(a -> a.getAccount().isActive() ? "1" : "0");
/**
* All emails (preferred email + secondary emails). Use this field only if the current user is
* allowed to see secondary emails (requires the 'Modify Account' capability).
*
* <p>Use the {@link AccountField#PREFERRED_EMAIL} if the current user can't see secondary emails.
*/
public static final FieldDef<AccountState, Iterable<String>> EMAIL =
prefix("email")
.buildRepeatable(
@ -145,5 +163,17 @@ public class AccountField {
.map(e -> e.toByteArray())
.collect(toSet()));
private static final Set<String> getNameParts(AccountState a, Iterable<String> emails) {
String fullName = a.getAccount().getFullName();
Set<String> parts = SchemaUtil.getNameParts(fullName, emails);
// Additional values not currently added by getPersonParts.
// TODO(dborowitz): Move to getPersonParts and remove this hack.
if (fullName != null) {
parts.add(fullName.toLowerCase(Locale.US));
}
return parts;
}
private AccountField() {}
}

View File

@ -40,7 +40,9 @@ public class AccountSchemaDefinitions extends SchemaDefinitions<AccountState> {
static final Schema<AccountState> V6 =
schema(V5, AccountField.REF_STATE, AccountField.EXTERNAL_ID_STATE);
static final Schema<AccountState> V7 = schema(V6, AccountField.PREFERRED_EMAIL_EXACT);
@Deprecated static final Schema<AccountState> V7 = schema(V6, AccountField.PREFERRED_EMAIL_EXACT);
static final Schema<AccountState> V8 = schema(V7, AccountField.NAME_PART_NO_SECONDARY_EMAIL);
public static final String NAME = "accounts";
public static final AccountSchemaDefinitions INSTANCE = new AccountSchemaDefinitions();