From f7569d0cb27df67728b4c717ba5e4ac2bb4b7080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20=C5=BDivkov?= Date: Mon, 9 Feb 2015 13:20:28 +0100 Subject: [PATCH] Perform user.getEffectiveGroups() less eagerly Most of the time we used the following pattern to check if a user is a member of one of the given groups: user.getEffectiveGroups().containsAnyOf(groups) The getEffectiveGroups used to eagerly fetch all groups where this user belongs to from all registered group backends. This included also recursive expansion of all LDAP groups this user is a member of. Even if the set of given groups was consisting of only local Gerrit groups the code above would still first fetch from all known group backends. This was particularly bad when using LDAP for user authentication but not using the LDAP groups otherwise. In large corporate setups a user could be a member of hundreds of LDAP groups which would all unnecessarily be fetched just to verify if the user is a member of a local group. Introduce the: GroupBacked.memberOfAny(user, groups) to enable a more lazy implementation. The UniversalGroupBackend will first partition the groups based on their group backends and then only fetch groups from the involved backends. For an LDAP based Gerrit instance which doesn't make use of LDAP groups this would effectively avoid fetching of the LDAP groups for this user. In our corporate setup this reduces the data transfered between the LDAP server and a Gerrit instance from 250KB down to 6KB, per user. Change-Id: I6e3027381cbf4cace454fa0cb9bfc725a2f452fa --- .../google/gerrit/server/AnonymousUser.java | 7 +++++ .../com/google/gerrit/server/CurrentUser.java | 10 +++++++ .../google/gerrit/server/IdentifiedUser.java | 6 +++++ .../google/gerrit/server/InternalUser.java | 6 +++++ .../google/gerrit/server/PeerDaemonUser.java | 6 +++++ .../server/account/AbstractGroupBackend.java | 26 +++++++++++++++++++ .../server/account/CapabilityControl.java | 15 +++-------- .../gerrit/server/account/GroupBackend.java | 8 ++++++ .../gerrit/server/account/GroupControl.java | 4 +-- .../account/IncludingGroupMembership.java | 2 +- .../server/account/InternalGroupBackend.java | 2 +- .../server/account/UniversalGroupBackend.java | 23 +++++++++++++++- .../server/auth/ldap/LdapGroupBackend.java | 4 +-- .../server/group/SystemGroupBackend.java | 4 +-- .../gerrit/server/project/ProjectControl.java | 10 +++---- .../query/change/EqualsLabelPredicate.java | 2 +- .../server/query/change/OwnerinPredicate.java | 2 +- .../google/gerrit/server/project/Util.java | 6 +++++ 18 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java index ff09df5674..2baa70090f 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java @@ -14,6 +14,8 @@ package com.google.gerrit.server; +import com.google.common.collect.Iterables; +import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.account.CapabilityControl; @@ -38,6 +40,11 @@ public class AnonymousUser extends CurrentUser { return new ListGroupMembership(Collections.singleton(SystemGroupBackend.ANONYMOUS_USERS)); } + @Override + public boolean memberOfAny(Iterable ids) { + return Iterables.contains(ids, SystemGroupBackend.ANONYMOUS_USERS); + } + @Override public Set getStarredChanges() { return Collections.emptySet(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java index 4f2c6b9c50..87ea9ff328 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java @@ -14,12 +14,14 @@ package com.google.gerrit.server; +import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.account.GroupMembership; import com.google.inject.servlet.RequestScoped; +import java.util.Arrays; import java.util.Collection; import java.util.Set; @@ -77,6 +79,14 @@ public abstract class CurrentUser { */ public abstract GroupMembership getEffectiveGroups(); + public boolean memberOfAny(Iterable ids) { + return getEffectiveGroups().containsAnyOf(ids); + } + + public boolean memberOf(AccountGroup.UUID id) { + return memberOfAny(Arrays.asList(id)); + } + /** Set of changes starred by this user. */ public abstract Set getStarredChanges(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java index be5e000c55..0477adba45 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java @@ -23,6 +23,7 @@ import com.google.gerrit.reviewdb.client.AccountDiffPreference; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.StarredChange; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountState; @@ -324,6 +325,11 @@ public class IdentifiedUser extends CurrentUser { return effectiveGroups; } + @Override + public boolean memberOfAny(Iterable ids) { + return groupBackend.memberOfAny(this, ids); + } + @Override public Set getStarredChanges() { if (starredChanges == null) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java index 6f5618bb7b..bd8ea14769 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/InternalUser.java @@ -16,6 +16,7 @@ package com.google.gerrit.server; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.account.GroupMembership; import com.google.inject.Inject; @@ -49,6 +50,11 @@ public class InternalUser extends CurrentUser { return GroupMembership.EMPTY; } + @Override + public boolean memberOfAny(Iterable ids) { + return false; + } + @Override public Set getStarredChanges() { return Collections.emptySet(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java index 4d26f024e9..6eb16822da 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java @@ -16,6 +16,7 @@ package com.google.gerrit.server; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; import com.google.gerrit.server.account.CapabilityControl; import com.google.gerrit.server.account.GroupMembership; import com.google.inject.Inject; @@ -49,6 +50,11 @@ public class PeerDaemonUser extends CurrentUser { return GroupMembership.EMPTY; } + @Override + public boolean memberOfAny(Iterable ids) { + return false; + } + @Override public Set getStarredChanges() { return Collections.emptySet(); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java new file mode 100644 index 0000000000..e0bc1a47af --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractGroupBackend.java @@ -0,0 +1,26 @@ +// Copyright (C) 2015 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.reviewdb.client.AccountGroup.UUID; +import com.google.gerrit.server.IdentifiedUser; + +public abstract class AbstractGroupBackend implements GroupBackend { + + @Override + public boolean memberOfAny(IdentifiedUser user, Iterable ids) { + return membershipsOf(user).containsAnyOf(ids); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java index 2721057483..5e6c0b7164 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java @@ -183,10 +183,9 @@ public class CapabilityControl { // the 'CI Servers' actually use the BATCH queue while everyone else gets // to use the INTERACTIVE queue without additional grants. // - GroupMembership groups = user.getEffectiveGroups(); boolean batch = false; for (PermissionRule r : capabilities.priority) { - if (match(groups, r)) { + if (user.memberOf(r.getGroup().getUUID())) { switch (r.getAction()) { case INTERACTIVE: if (!SystemGroupBackend.isAnonymousOrRegistered(r.getGroup())) { @@ -265,9 +264,8 @@ public class CapabilityControl { return rules; } - GroupMembership groups = user.getEffectiveGroups(); if (rules.size() == 1) { - if (!match(groups, rules.get(0))) { + if (!user.memberOf(rules.get(0).getGroup().getUUID())) { rules = Collections.emptyList(); } effective.put(permissionName, rules); @@ -276,7 +274,7 @@ public class CapabilityControl { List mine = new ArrayList<>(rules.size()); for (PermissionRule rule : rules) { - if (match(groups, rule)) { + if (user.memberOf(rule.getGroup().getUUID())) { mine.add(rule); } } @@ -304,11 +302,6 @@ public class CapabilityControl { return rule.getGroup().getUUID(); } }); - return user.getEffectiveGroups().containsAnyOf(ids); - } - - private static boolean match(GroupMembership groups, - PermissionRule rule) { - return groups.contains(rule.getGroup().getUUID()); + return user.memberOfAny(ids); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java index 43b94f3383..841b9c2fe0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupBackend.java @@ -50,4 +50,12 @@ public interface GroupBackend { /** @return the group membership checker for the backend. */ GroupMembership membershipsOf(IdentifiedUser user); + + /** + * Check if the user is member of any of the given groups. + * + * @return true if the user is a member of at least one of the + * given groups, false otherwise + */ + boolean memberOfAny(IdentifiedUser user, Iterable ids); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java index a3a280936e..de87acb267 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java @@ -133,7 +133,7 @@ public class GroupControl { */ return (accountGroup != null && accountGroup.isVisibleToAll()) || user instanceof InternalUser - || user.getEffectiveGroups().contains(group.getGroupUUID()) + || user.memberOf(group.getGroupUUID()) || isOwner() || user.getCapabilities().canAdministrateServer(); } @@ -144,7 +144,7 @@ public class GroupControl { isOwner = false; } else if (isOwner == null) { AccountGroup.UUID ownerUUID = accountGroup.getOwnerGroupUUID(); - isOwner = getCurrentUser().getEffectiveGroups().contains(ownerUUID) + isOwner = getCurrentUser().memberOf(ownerUUID) || getCurrentUser().getCapabilities().canAdministrateServer(); } return isOwner; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java index b8a67ff791..3eb192ebec 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java @@ -112,7 +112,7 @@ public class IncludingGroupMembership implements GroupMembership { } private boolean search(Set ids) { - return user.getEffectiveGroups().containsAnyOf(ids); + return user.memberOfAny(ids); } private ImmutableSet computeKnownGroups() { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java index a70f9429a2..55b359c91e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java @@ -31,7 +31,7 @@ import java.util.Collection; /** Implementation of GroupBackend for the internal group system. */ @Singleton -public class InternalGroupBackend implements GroupBackend { +public class InternalGroupBackend extends AbstractGroupBackend { private static final Function ACT_GROUP_TO_GROUP_REF = new Function() { @Override diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java index 1748395dda..401f180897 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java @@ -19,6 +19,7 @@ import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMP import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.gerrit.common.Nullable; @@ -26,6 +27,7 @@ import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.project.ProjectControl; import com.google.inject.Inject; @@ -43,7 +45,7 @@ import java.util.Set; * set of GroupBackends. */ @Singleton -public class UniversalGroupBackend implements GroupBackend { +public class UniversalGroupBackend extends AbstractGroupBackend { private static final Logger log = LoggerFactory.getLogger(UniversalGroupBackend.class); @@ -95,6 +97,25 @@ public class UniversalGroupBackend implements GroupBackend { return new UniversalGroupMembership(user); } + @Override + public boolean memberOfAny(IdentifiedUser user, Iterable ids) { + Multimap groups = LinkedListMultimap.create(); + for (AccountGroup.UUID uuid : ids) { + for (GroupBackend g : backends) { + if (g.handles(uuid)) { + groups.put(g, uuid); + } + } + } + + for (Map.Entry> e : groups.asMap().entrySet()) { + if (e.getKey().memberOfAny(user, e.getValue())) { + return true; + } + } + return false; + } + private class UniversalGroupMembership implements GroupMembership { private final Map memberships; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java index 7731b7de07..0bc5e44141 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java @@ -29,7 +29,7 @@ import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.account.AbstractGroupBackend; import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.account.ListGroupMembership; import com.google.gerrit.server.auth.ldap.Helper.LdapSchema; @@ -59,7 +59,7 @@ import javax.security.auth.login.LoginException; /** * Implementation of GroupBackend for the LDAP group system. */ -public class LdapGroupBackend implements GroupBackend { +public class LdapGroupBackend extends AbstractGroupBackend { private static final Logger log = LoggerFactory.getLogger(LdapGroupBackend.class); private static final String LDAP_NAME = "ldap/"; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java index c888f73722..59ca27278c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/SystemGroupBackend.java @@ -22,7 +22,7 @@ import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.account.AbstractGroupBackend; import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.account.ListGroupMembership; import com.google.gerrit.server.project.ProjectControl; @@ -36,7 +36,7 @@ import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; -public class SystemGroupBackend implements GroupBackend { +public class SystemGroupBackend extends AbstractGroupBackend { /** Common UUID assigned to the "Anonymous Users" group. */ public static final AccountGroup.UUID ANONYMOUS_USERS = new AccountGroup.UUID("global:Anonymous-Users"); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index 3bbcf718e3..9d07491a6c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -34,7 +34,6 @@ import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.InternalUser; -import com.google.gerrit.server.account.GroupMembership; import com.google.gerrit.server.change.IncludedInResolver; import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.GitReceivePackGroups; @@ -299,8 +298,7 @@ public class ProjectControl { private boolean isDeclaredOwner() { if (declaredOwner == null) { - GroupMembership effectiveGroups = user.getEffectiveGroups(); - declaredOwner = effectiveGroups.containsAnyOf(state.getAllOwners()); + declaredOwner = user.memberOfAny(state.getAllOwners()); } return declaredOwner; } @@ -374,11 +372,11 @@ public class ProjectControl { } } - if (iUser.getEffectiveGroups().containsAnyOf(okGroupIds)) { + if (iUser.memberOfAny(okGroupIds)) { return Capable.OK; } - if (iUser.getEffectiveGroups().containsAnyOf(missingInfoGroupIds)) { + if (iUser.memberOfAny(missingInfoGroupIds)) { final StringBuilder msg = new StringBuilder(); for (ContributorAgreement ca : contributorAgreements) { if (ca.isRequireContactInformation()) { @@ -512,7 +510,7 @@ public class ProjectControl { } else if (SystemGroupBackend.CHANGE_OWNER.equals(uuid)) { return isChangeOwner; } else { - return user.getEffectiveGroups().contains(uuid); + return user.memberOf(uuid); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java index 173060b829..9b4dbc7f12 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java @@ -131,7 +131,7 @@ class EqualsLabelPredicate extends IndexPredicate { return false; } - if (group != null && !reviewer.getEffectiveGroups().contains(group)) { + if (group != null && !reviewer.memberOf(group)) { return false; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java index a0c123513d..60f7c0ea6a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java @@ -47,7 +47,7 @@ class OwnerinPredicate extends OperatorPredicate { } final IdentifiedUser owner = userFactory.create(dbProvider, change.getOwner()); - return owner.getEffectiveGroups().contains(uuid); + return owner.memberOf(uuid); } @Override diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java index e39700cddc..556023c132 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java @@ -29,6 +29,7 @@ import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountProjectWatch; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.rules.PrologEnvironment; import com.google.gerrit.rules.RulesCache; @@ -356,6 +357,11 @@ public class Util { return groups; } + @Override + public boolean memberOfAny(Iterable ids) { + return getEffectiveGroups().containsAnyOf(ids); + } + @Override public String getUserName() { return username;