Abstract group systems into GroupBackend interface.

Currently, every NameKey and UUID for a group is expected to be backed
by an entry in the AccountGroup table. In the future, the group
backend will be swapable by allowing implementations to scope
AccountGroup.UUIDs e.g. "ldap:<ldap identifier>". This change
adds a GroupBackend interface that abstracts the implementation details
of a group system with respect to looking up groups (editing and
creating are not supported), which should be used instead of GroupCache
when looking up by AccountGroup.UUID or name. GroupBackends scope which
AccountGroup.UUIDs they handle by implementing the handles() method for
a particular prefix. The UniversalGroupBackend is a GroupBackend that
delegates methods to the GroupBackend that handles() the UUID, using
DynamicSets. The InternalGroupBackend and LdapGroupBackend are
the first GroupBackend implementation. It should be possible to bind
many GroupBackends concurrently, unfortunately, the current
implementation of LdapGroupBackend.handles() overlaps with the
InternalGroupBackend.handles() (since they are both stored in the
database), so only one those implementations may be bound at once.

Change-Id: Ieda21916247084ebc2dffc9dd82d3e1230b9ab64
This commit is contained in:
Colby Ranger
2012-05-02 09:02:03 -07:00
parent 8363aa5144
commit 2ac82b021f
34 changed files with 965 additions and 234 deletions

View File

@@ -0,0 +1,48 @@
// Copyright (C) 2012 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.common.data;
import com.google.gerrit.reviewdb.client.AccountGroup;
/**
* Group methods exposed by the GroupBackend.
*/
public class GroupDescription {
/**
* The Basic information required to be exposed by any Group.
*/
public interface Basic {
/** @return the non-null UUID of the group. */
AccountGroup.UUID getGroupUUID();
/** @return the non-null name of the group. */
String getName();
/** @return whether the group is visible to all accounts. */
boolean isVisibleToAll();
}
/**
* The extended information exposed by internal groups backed by an
* AccountGroup.
*/
public interface Internal extends Basic {
/** @return the backing AccountGroup. */
AccountGroup getAccountGroup();
}
private GroupDescription() {
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (C) 2012 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.common.data;
import com.google.gerrit.reviewdb.client.AccountGroup;
import javax.annotation.Nullable;
/**
* Utility class for building GroupDescription objects.
*/
public class GroupDescriptions {
@Nullable
public static AccountGroup toAccountGroup(GroupDescription.Basic group) {
if (group instanceof GroupDescription.Internal) {
return ((GroupDescription.Internal) group).getAccountGroup();
}
return null;
}
public static GroupDescription.Internal forAccountGroup(final AccountGroup group) {
return new GroupDescription.Internal() {
@Override
public AccountGroup.UUID getGroupUUID() {
return group.getGroupUUID();
}
@Override
public String getName() {
return group.getName();
}
@Override
public boolean isVisibleToAll() {
return group.isVisibleToAll();
}
@Override
public AccountGroup getAccountGroup() {
return group;
}
};
}
private GroupDescriptions() {
}
}

View File

@@ -23,6 +23,10 @@ public class GroupReference implements Comparable<GroupReference> {
return new GroupReference(group.getGroupUUID(), group.getName());
}
public static GroupReference forGroup(GroupDescription.Basic group) {
return new GroupReference(group.getGroupUUID(), group.getName());
}
protected String uuid;
protected String name;

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.OrmRuntimeException;
import com.google.inject.Provider;
/** Support for services which require a {@link ReviewDb} instance. */
@@ -70,20 +71,14 @@ public class BaseServiceImplementation {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchGroupException e) {
callback.onFailure(new NoSuchEntityException());
} catch (OrmException e) {
if (e.getCause() instanceof Failure) {
callback.onFailure(e.getCause().getCause());
} else if (e.getCause() instanceof CorruptEntityException) {
callback.onFailure(e.getCause());
} else if (e.getCause() instanceof NoSuchEntityException) {
callback.onFailure(e.getCause());
} else {
callback.onFailure(e);
} catch (OrmRuntimeException e) {
Exception ex = e;
if (e.getCause() instanceof OrmException) {
ex = (OrmException) e.getCause();
}
handleOrmException(callback, ex);
} catch (OrmException e) {
handleOrmException(callback, e);
} catch (Failure e) {
if (e.getCause() instanceof NoSuchProjectException
|| e.getCause() instanceof NoSuchChangeException) {
@@ -95,6 +90,19 @@ public class BaseServiceImplementation {
}
}
private static <T> void handleOrmException(
final AsyncCallback<T> callback, Exception e) {
if (e.getCause() instanceof Failure) {
callback.onFailure(e.getCause().getCause());
} else if (e.getCause() instanceof CorruptEntityException) {
callback.onFailure(e.getCause());
} else if (e.getCause() instanceof NoSuchEntityException) {
callback.onFailure(e.getCause());
} else {
callback.onFailure(e);
}
}
/** Exception whose cause is passed into onFailure. */
public static class Failure extends Exception {
private static final long serialVersionUID = 1L;

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ReviewerInfo;
@@ -21,8 +22,6 @@ import com.google.gerrit.common.data.SuggestService;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -31,7 +30,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountVisibility;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -65,7 +64,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
private final AccountControl.Factory accountControlFactory;
private final ChangeControl.Factory changeControlFactory;
private final Config cfg;
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final boolean suggestAccounts;
@Inject
@@ -77,7 +76,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
final IdentifiedUser.GenericFactory identifiedUserFactory,
final AccountControl.Factory accountControlFactory,
final ChangeControl.Factory changeControlFactory,
@GerritServerConfig final Config cfg, final GroupCache groupCache) {
@GerritServerConfig final Config cfg, final GroupBackend groupBackend) {
super(schema, currentUser);
this.reviewDbProvider = schema;
this.accountCache = accountCache;
@@ -87,7 +86,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
this.accountControlFactory = accountControlFactory;
this.changeControlFactory = changeControlFactory;
this.cfg = cfg;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
if ("OFF".equals(cfg.getString("suggest", null, "accounts"))) {
this.suggestAccounts = false;
@@ -177,32 +176,28 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<GroupReference>> callback) {
run(callback, new Action<List<GroupReference>>() {
public List<GroupReference> run(final ReviewDb db) throws OrmException {
return suggestAccountGroup(db, query, limit);
public List<GroupReference> run(final ReviewDb db) {
return suggestAccountGroup(query, limit);
}
});
}
private List<GroupReference> suggestAccountGroup(final ReviewDb db,
final String query, final int limit) throws OrmException {
final String a = query;
final String b = a + MAX_SUFFIX;
final int max = 10;
final int n = limit <= 0 ? max : Math.min(limit, max);
List<GroupReference> r = new ArrayList<GroupReference>(n);
for (AccountGroupName group : db.accountGroupNames().suggestByName(a, b, n)) {
private List<GroupReference> suggestAccountGroup(final String query, final int limit) {
final int n = limit <= 0 ? 10 : Math.min(limit, 10);
List<GroupReference> out = Lists.newArrayListWithCapacity(n);
for (GroupReference g : groupBackend.suggest(query)) {
try {
if (groupControlFactory.controlFor(group.getId()).isVisible()) {
AccountGroup g = groupCache.get(group.getId());
if (g != null && g.getGroupUUID() != null) {
r.add(GroupReference.forGroup(g));
if (groupControlFactory.controlFor(g.getUUID()).isVisible()) {
out.add(g);
if (out.size() == n) {
break;
}
}
} catch (NoSuchGroupException e) {
continue;
}
}
return r;
return out;
}
@Override
@@ -243,7 +238,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
reviewer.add(new ReviewerInfo(a));
}
final List<GroupReference> suggestedAccountGroups =
suggestAccountGroup(db, query, limit);
suggestAccountGroup(query, limit);
for (final GroupReference g : suggestedAccountGroups) {
if (suggestGroupAsReviewer(changeControl.getProject().getNameKey(), g)) {
reviewer.add(new ReviewerInfo(g));

View File

@@ -18,6 +18,7 @@ import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupOptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.InactiveAccountException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
@@ -34,6 +35,8 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCache;
@@ -44,7 +47,6 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
@@ -53,6 +55,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
private final AccountCache accountCache;
private final AccountResolver accountResolver;
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final GroupIncludeCache groupIncludeCache;
private final GroupControl.Factory groupControlFactory;
@@ -68,6 +71,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
final GroupIncludeCache groupIncludeCache,
final AccountResolver accountResolver,
final GroupCache groupCache,
final GroupBackend groupBackend,
final GroupControl.Factory groupControlFactory,
final CreateGroup.Factory createGroupFactory,
final RenameGroup.Factory renameGroupFactory,
@@ -78,6 +82,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
this.groupIncludeCache = groupIncludeCache;
this.accountResolver = accountResolver;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.groupControlFactory = groupControlFactory;
this.createGroupFactory = createGroupFactory;
this.renameGroupFactory = renameGroupFactory;
@@ -140,13 +145,13 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
AccountGroup owner =
groupCache.get(new AccountGroup.NameKey(newOwnerName));
GroupReference owner =
GroupBackends.findExactSuggestion(groupBackend, newOwnerName);
if (owner == null) {
throw new Failure(new NoSuchEntityException());
}
group.setOwnerGroupUUID(owner.getGroupUUID());
group.setOwnerGroupUUID(owner.getUUID());
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
@@ -179,7 +184,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -214,7 +219,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -247,7 +252,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
@@ -301,7 +306,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
if (groupCache.get(groupId).getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}

View File

@@ -22,9 +22,9 @@ import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -60,7 +60,7 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
private final ProjectAccessFactory.Factory projectAccessFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final Project.NameKey projectName;
@@ -71,7 +71,7 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
@Inject
ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final GroupCache groupCache,
final ProjectCache projectCache, final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory,
@Assisted final Project.NameKey projectName,
@@ -81,7 +81,7 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
this.projectAccessFactory = projectAccessFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectName = projectName;
@@ -198,12 +198,12 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
GroupReference ref = rule.getGroup();
if (ref.getUUID() == null) {
AccountGroup.NameKey name = new AccountGroup.NameKey(ref.getName());
AccountGroup group = groupCache.get(name);
final GroupReference group =
GroupBackends.findBestSuggestion(groupBackend, ref.getName());
if (group == null) {
throw new NoSuchGroupException(name);
throw new NoSuchGroupException(ref.getName());
}
ref.setUUID(group.getGroupUUID());
ref.setUUID(group.getUUID());
}
}
}

View File

@@ -23,7 +23,7 @@ import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -51,7 +51,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
ProjectAccessFactory create(@Assisted Project.NameKey name);
}
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final ProjectCache projectCache;
private final ProjectControl.Factory projectControlFactory;
private final GroupControl.Factory groupControlFactory;
@@ -62,7 +62,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
private ProjectControl pc;
@Inject
ProjectAccessFactory(final GroupCache groupCache,
ProjectAccessFactory(final GroupBackend groupBackend,
final ProjectCache projectCache,
final ProjectControl.Factory projectControlFactory,
final GroupControl.Factory groupControlFactory,
@@ -70,7 +70,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
final AllProjectsName allProjectsName,
@Assisted final Project.NameKey name) {
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.projectCache = projectCache;
this.projectControlFactory = projectControlFactory;
this.groupControlFactory = groupControlFactory;
@@ -94,7 +94,7 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
try {
config = ProjectConfig.read(md);
if (config.updateGroupNames(groupCache)) {
if (config.updateGroupNames(groupBackend)) {
md.setMessage("Update group names\n");
if (config.commit(md)) {
projectCache.evict(config.getProject());

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -24,8 +25,9 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.MaterializedGroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.AuthConfig;
@@ -46,13 +48,10 @@ import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.URL;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@@ -70,7 +69,7 @@ public class IdentifiedUser extends CurrentUser {
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
private final GroupBackend groupBackend;
@Inject
GenericFactory(
@@ -79,14 +78,14 @@ public class IdentifiedUser extends CurrentUser {
final @AnonymousCowardName String anonymousCowardName,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final MaterializedGroupMembership.Factory groupMembershipFactory) {
final GroupBackend groupBackend) {
this.capabilityControlFactory = capabilityControlFactory;
this.authConfig = authConfig;
this.anonymousCowardName = anonymousCowardName;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
this.groupMembershipFactory = groupMembershipFactory;
this.groupBackend = groupBackend;
}
public IdentifiedUser create(final Account.Id id) {
@@ -96,14 +95,14 @@ public class IdentifiedUser extends CurrentUser {
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, AccessPath.UNKNOWN,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
groupMembershipFactory, null, db, id);
groupBackend, null, db, id);
}
public IdentifiedUser create(AccessPath accessPath,
Provider<SocketAddress> remotePeerProvider, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, accessPath,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
groupMembershipFactory, remotePeerProvider, null, id);
groupBackend, remotePeerProvider, null, id);
}
}
@@ -121,7 +120,7 @@ public class IdentifiedUser extends CurrentUser {
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
private final GroupBackend groupBackend;
private final Provider<SocketAddress> remotePeerProvider;
private final Provider<ReviewDb> dbProvider;
@@ -133,7 +132,7 @@ public class IdentifiedUser extends CurrentUser {
final @AnonymousCowardName String anonymousCowardName,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final MaterializedGroupMembership.Factory groupMembershipFactory,
final GroupBackend groupBackend,
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) {
@@ -143,7 +142,7 @@ public class IdentifiedUser extends CurrentUser {
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
this.groupMembershipFactory = groupMembershipFactory;
this.groupBackend = groupBackend;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
@@ -153,40 +152,22 @@ public class IdentifiedUser extends CurrentUser {
final Account.Id id) {
return new IdentifiedUser(capabilityControlFactory, accessPath,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
groupMembershipFactory, remotePeerProvider, dbProvider, id);
groupBackend, remotePeerProvider, dbProvider, id);
}
}
private static final Logger log =
LoggerFactory.getLogger(IdentifiedUser.class);
private static final Set<AccountGroup.UUID> registeredGroups =
new AbstractSet<AccountGroup.UUID>() {
private final List<AccountGroup.UUID> groups =
Collections.unmodifiableList(Arrays.asList(new AccountGroup.UUID[] {
AccountGroup.ANONYMOUS_USERS, AccountGroup.REGISTERED_USERS}));
@Override
public boolean contains(Object o) {
return groups.contains(o);
}
@Override
public Iterator<AccountGroup.UUID> iterator() {
return groups.iterator();
}
@Override
public int size() {
return groups.size();
}
};
private static final GroupMembership registeredGroups =
new ListGroupMembership(ImmutableSet.of(
AccountGroup.ANONYMOUS_USERS,
AccountGroup.REGISTERED_USERS));
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
private final AuthConfig authConfig;
private final GroupBackend groupBackend;
private final String anonymousCowardName;
@Nullable
@@ -210,14 +191,13 @@ public class IdentifiedUser extends CurrentUser {
final String anonymousCowardName,
final Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
final MaterializedGroupMembership.Factory groupMembershipFactory,
final GroupBackend groupBackend,
@Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
super(capabilityControlFactory, accessPath);
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
this.groupMembershipFactory = groupMembershipFactory;
this.groupBackend = groupBackend;
this.authConfig = authConfig;
this.anonymousCowardName = anonymousCowardName;
this.remotePeerProvider = remotePeerProvider;
@@ -225,7 +205,8 @@ public class IdentifiedUser extends CurrentUser {
this.accountId = id;
}
private AccountState state() {
// TODO(cranger): maybe get the state through the accountCache instead.
public AccountState state() {
if (state == null) {
state = accountCache.get(getAccountId());
}
@@ -272,12 +253,11 @@ public class IdentifiedUser extends CurrentUser {
public GroupMembership getEffectiveGroups() {
if (effectiveGroups == null) {
if (authConfig.isIdentityTrustable(state().getExternalIds())) {
effectiveGroups = realm.groups(state());
effectiveGroups = groupBackend.membershipsOf(this);
} else {
effectiveGroups = groupMembershipFactory.create(registeredGroups);
effectiveGroups = registeredGroups;
}
}
return effectiveGroups;
}

View File

@@ -15,25 +15,20 @@
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Set;
public class DefaultRealm implements Realm {
private final EmailExpander emailExpander;
private final AccountByEmailCache byEmail;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
@Inject
DefaultRealm(final EmailExpander emailExpander,
final AccountByEmailCache byEmail,
final MaterializedGroupMembership.Factory groupMembershipFactory) {
final AccountByEmailCache byEmail) {
this.emailExpander = emailExpander;
this.byEmail = byEmail;
this.groupMembershipFactory = groupMembershipFactory;
}
@Override
@@ -64,11 +59,6 @@ public class DefaultRealm implements Realm {
public void onCreateAccount(final AuthRequest who, final Account account) {
}
@Override
public GroupMembership groups(final AccountState who) {
return groupMembershipFactory.create(who.getInternalGroups());
}
@Override
public Account.Id lookup(final String accountName) {
if (emailExpander.canExpand(accountName)) {

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2012 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.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import java.util.Collection;
import javax.annotation.Nullable;
/**
* Implementations of GroupBackend provide lookup and membership accessors
* to a group system.
*/
public interface GroupBackend {
/** @return {@code true} if the backend can operate on the UUID. */
boolean handles(AccountGroup.UUID uuid);
/**
* Looks up a group in the backend. If the group does not exist, null is
* returned.
*
* @param uuid the group identifier
* @return the group
*/
@Nullable
GroupDescription.Basic get(AccountGroup.UUID uuid);
/** @return suggestions for the group name sorted by name. */
Collection<GroupReference> suggest(String name);
/** @return the group membership checker for the backend. */
GroupMembership membershipsOf(IdentifiedUser user);
}

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2012 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.common.collect.Iterables;
import com.google.gerrit.common.data.GroupReference;
import java.util.Collection;
import java.util.Comparator;
import javax.annotation.Nullable;
/**
* Utility class for dealing with a GroupBackend.
*/
public class GroupBackends {
public static final Comparator<GroupReference> GROUP_REF_NAME_COMPARATOR =
new Comparator<GroupReference>() {
@Override
public int compare(GroupReference a, GroupReference b) {
return a.getName().compareTo(b.getName());
}
};
/**
* Runs {@link GroupBackend#suggest(String)} and filters the result to return
* the best suggestion, or null if one does not exist.
*
* @param groupBackend the group backend
* @param name the name for which to suggest groups
* @return the best single GroupReference suggestion
*/
@Nullable
public static GroupReference findBestSuggestion(
GroupBackend groupBackend, String name) {
Collection<GroupReference> refs = groupBackend.suggest(name);
if (refs.size() == 1) {
return Iterables.getOnlyElement(refs);
}
for (GroupReference ref : refs) {
if (isExactSuggestion(ref, name)) {
return ref;
}
}
return null;
}
/**
* Runs {@link GroupBackend#suggest(String)} and filters the result to return
* the exact suggestion, or null if one does not exist.
*
* @param groupBackend the group backend
* @param name the name for which to suggest groups
* @return the exact single GroupReference suggestion
*/
@Nullable
public static GroupReference findExactSuggestion(
GroupBackend groupBackend, String name) {
Collection<GroupReference> refs = groupBackend.suggest(name);
for (GroupReference ref : refs) {
if (isExactSuggestion(ref, name)) {
return ref;
}
}
return null;
}
/** Returns whether the GroupReference is an exact suggestion for the name. */
public static boolean isExactSuggestion(GroupReference ref, String name) {
return ref.getName().equalsIgnoreCase(name) || ref.getUUID().get().equals(name);
}
private GroupBackends() {
}
}

View File

@@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -27,11 +29,14 @@ public class GroupControl {
public static class Factory {
private final GroupCache groupCache;
private final Provider<CurrentUser> user;
private final GroupBackend groupBackend;
@Inject
Factory(final GroupCache gc, final Provider<CurrentUser> cu) {
Factory(final GroupCache gc, final Provider<CurrentUser> cu,
final GroupBackend gb) {
groupCache = gc;
user = cu;
groupBackend = gb;
}
public GroupControl controlFor(final AccountGroup.Id groupId)
@@ -45,7 +50,7 @@ public class GroupControl {
public GroupControl controlFor(final AccountGroup.UUID groupId)
throws NoSuchGroupException {
final AccountGroup group = groupCache.get(groupId);
final GroupDescription.Basic group = groupBackend.get(groupId);
if (group == null) {
throw new NoSuchGroupException(groupId);
}
@@ -67,22 +72,22 @@ public class GroupControl {
}
private final CurrentUser user;
private final AccountGroup group;
private final GroupDescription.Basic group;
private Boolean isOwner;
GroupControl(CurrentUser who, AccountGroup gc) {
GroupControl(CurrentUser who, GroupDescription.Basic gd) {
user = who;
group = gc;
group = gd;
}
GroupControl(CurrentUser who, AccountGroup ag) {
this(who, GroupDescriptions.forAccountGroup(ag));
}
public CurrentUser getCurrentUser() {
return user;
}
public AccountGroup getAccountGroup() {
return group;
}
/** Can this user see this group exists? */
public boolean isVisible() {
return group.isVisibleToAll()
@@ -91,8 +96,11 @@ public class GroupControl {
}
public boolean isOwner() {
if (isOwner == null) {
AccountGroup.UUID ownerUUID = group.getOwnerGroupUUID();
AccountGroup accountGroup = GroupDescriptions.toAccountGroup(group);
if (accountGroup == null) {
isOwner = false;
} else if (isOwner == null) {
AccountGroup.UUID ownerUUID = accountGroup.getOwnerGroupUUID();
isOwner = getCurrentUser().getEffectiveGroups().contains(ownerUUID)
|| getCurrentUser().getCapabilities().canAdministrateServer();
}

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
@@ -40,6 +41,7 @@ public class GroupDetailFactory implements Callable<GroupDetail> {
private final ReviewDb db;
private final GroupControl.Factory groupControl;
private final GroupCache groupCache;
private final GroupBackend groupBackend;
private final AccountInfoCacheFactory aic;
private final GroupInfoCacheFactory gic;
@@ -49,12 +51,14 @@ public class GroupDetailFactory implements Callable<GroupDetail> {
@Inject
GroupDetailFactory(final ReviewDb db,
final GroupControl.Factory groupControl, final GroupCache groupCache,
final GroupBackend groupBackend,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final GroupInfoCacheFactory.Factory groupInfoCacheFactory,
@Assisted final AccountGroup.Id groupId) {
this.db = db;
this.groupControl = groupControl;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.aic = accountInfoCacheFactory.create();
this.gic = groupInfoCacheFactory.create();
@@ -64,10 +68,10 @@ public class GroupDetailFactory implements Callable<GroupDetail> {
@Override
public GroupDetail call() throws OrmException, NoSuchGroupException {
control = groupControl.validateFor(groupId);
final AccountGroup group = control.getAccountGroup();
final AccountGroup group = groupCache.get(groupId);
final GroupDetail detail = new GroupDetail();
detail.setGroup(group);
AccountGroup ownerGroup = groupCache.get(group.getOwnerGroupUUID());
GroupDescription.Basic ownerGroup = groupBackend.get(group.getOwnerGroupUUID());
if (ownerGroup != null) {
detail.setOwnerGroup(GroupReference.forGroup(ownerGroup));
}

View File

@@ -24,7 +24,6 @@ import java.util.Set;
* the presence of a user in a particular group.
*/
public interface GroupMembership {
public static final GroupMembership EMPTY =
new ListGroupMembership(Collections.<AccountGroup.UUID>emptySet());
@@ -45,7 +44,7 @@ public interface GroupMembership {
* This may not return all groups the {@link #contains(AccountGroup.UUID)}
* would return {@code true} for, but will at least contain all top level
* groups. This restriction stems from the API of some group systems, which
* make it expensive to enumate the members of a group.
* make it expensive to enumerate the members of a group.
*/
Set<AccountGroup.UUID> getKnownGroups();
}

View File

@@ -25,11 +25,12 @@ import java.util.Queue;
import java.util.Set;
/**
* Creates a GroupMembership object from materialized collection of groups.
* Creates a GroupMembership checker for the internal group system, which
* starts with the seed groups and includes all child groups.
*/
public class MaterializedGroupMembership implements GroupMembership {
public class IncludingGroupMembership implements GroupMembership {
public interface Factory {
MaterializedGroupMembership create(Iterable<AccountGroup.UUID> groupIds);
IncludingGroupMembership create(Iterable<AccountGroup.UUID> groupIds);
}
private final GroupIncludeCache groupIncludeCache;
@@ -37,7 +38,7 @@ public class MaterializedGroupMembership implements GroupMembership {
private final Queue<AccountGroup.UUID> groupQueue;
@Inject
MaterializedGroupMembership(
IncludingGroupMembership(
GroupIncludeCache groupIncludeCache,
@Assisted Iterable<AccountGroup.UUID> seedGroups) {
this.groupIncludeCache = groupIncludeCache;

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2012 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.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collection;
/**
* Implementation of GroupBackend for the internal group system.
*/
@Singleton
public class InternalGroupBackend implements GroupBackend {
private static final Function<AccountGroup, GroupReference> ACT_GROUP_TO_GROUP_REF =
new Function<AccountGroup, GroupReference>() {
@Override
public GroupReference apply(AccountGroup group) {
return GroupReference.forGroup(group);
}
};
private final GroupCache groupCache;
private final IncludingGroupMembership.Factory groupMembershipFactory;
@Inject
InternalGroupBackend(GroupCache groupCache,
IncludingGroupMembership.Factory groupMembershipFactory) {
this.groupCache = groupCache;
this.groupMembershipFactory = groupMembershipFactory;
}
@Override
public boolean handles(AccountGroup.UUID uuid) {
return uuid.get().startsWith("global:")
|| uuid.get().matches("[0-9a-f]{40}");
}
@Override
public GroupDescription.Internal get(AccountGroup.UUID uuid) {
if (!handles(uuid)) {
return null;
}
AccountGroup g = groupCache.get(uuid);
if (g == null) {
return null;
}
return GroupDescriptions.forAccountGroup(g);
}
@Override
public Collection<GroupReference> suggest(final String name) {
Iterable<AccountGroup> filtered = Iterables.filter(groupCache.all(),
new Predicate<AccountGroup>() {
@Override
public boolean apply(AccountGroup group) {
// startsWithIgnoreCase
return group.getName().regionMatches(true, 0, name, 0, name.length());
}
});
return Lists.newArrayList(Iterables.transform(filtered, ACT_GROUP_TO_GROUP_REF));
}
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
return groupMembershipFactory.create(user.state().getInternalGroups());
}
}

View File

@@ -15,11 +15,8 @@
package com.google.gerrit.server.account;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import java.util.Set;
public interface Realm {
/** Can the end-user modify this field of their own account? */
public boolean allowsEdit(Account.FieldName field);
@@ -34,8 +31,6 @@ public interface Realm {
public void onCreateAccount(AuthRequest who, Account account);
public GroupMembership groups(AccountState who);
/**
* Locate an account whose local username is the given account name.
* <p>

View File

@@ -0,0 +1,161 @@
// Copyright (C) 2012 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 static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
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.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Universal implementation of the GroupBackend that works with the injected
* set of GroupBackends.
*/
@Singleton
public class UniversalGroupBackend implements GroupBackend {
private static final Logger log =
LoggerFactory.getLogger(UniversalGroupBackend.class);
private final DynamicSet<GroupBackend> backends;
@Inject
UniversalGroupBackend(DynamicSet<GroupBackend> backends) {
this.backends = backends;
}
@Nullable
private GroupBackend backend(AccountGroup.UUID uuid) {
if (uuid != null) {
for (GroupBackend g : backends) {
if (g.handles(uuid)) {
return g;
}
}
}
return null;
}
@Override
public boolean handles(AccountGroup.UUID uuid) {
return backend(uuid) != null;
}
@Override
public GroupDescription.Basic get(AccountGroup.UUID uuid) {
GroupBackend b = backend(uuid);
if (b == null) {
log.warn("Unknown GroupBackend for UUID: " + uuid);
return null;
}
return b.get(uuid);
}
@Override
public Collection<GroupReference> suggest(String name) {
Set<GroupReference> groups = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
for (GroupBackend g : backends) {
groups.addAll(g.suggest(name));
}
return groups;
}
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
return new UniversalGroupMembership(user);
}
private class UniversalGroupMembership implements GroupMembership {
private final Map<GroupBackend, GroupMembership> memberships;
private UniversalGroupMembership(IdentifiedUser user) {
ImmutableMap.Builder<GroupBackend, GroupMembership> builder =
ImmutableMap.builder();
for (GroupBackend g : backends) {
builder.put(g, g.membershipsOf(user));
}
this.memberships = builder.build();
}
@Nullable
private GroupMembership membership(AccountGroup.UUID uuid) {
if (uuid != null) {
for (Map.Entry<GroupBackend, GroupMembership> m : memberships.entrySet()) {
if (m.getKey().handles(uuid)) {
return m.getValue();
}
}
}
return null;
}
@Override
public boolean contains(AccountGroup.UUID uuid) {
GroupMembership m = membership(uuid);
if (m == null) {
log.warn("Unknown GroupMembership for UUID: " + uuid);
return false;
}
return m.contains(uuid);
}
@Override
public boolean containsAnyOf(Iterable<AccountGroup.UUID> uuids) {
Multimap<GroupMembership, AccountGroup.UUID> lookups =
ArrayListMultimap.create();
for (AccountGroup.UUID uuid : uuids) {
GroupMembership m = membership(uuid);
if (m == null) {
log.warn("Unknown GroupMembership for UUID: " + uuid);
continue;
}
lookups.put(m, uuid);
}
for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry :
lookups.asMap().entrySet()) {
if (entry.getKey().containsAnyOf(entry.getValue())) {
return true;
}
}
return false;
}
@Override
public Set<AccountGroup.UUID> getKnownGroups() {
Set<AccountGroup.UUID> groups = Sets.newHashSet();
for (GroupMembership m : memberships.values()) {
groups.addAll(m.getKnownGroups());
}
return groups;
}
}
}

View File

@@ -0,0 +1,227 @@
// Copyright (C) 2012 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.auth.ldap;
import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
import static com.google.gerrit.server.auth.ldap.Helper.LDAP_UUID;
import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_CACHE;
import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_EXIST_CACHE;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ParameterizedString;
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.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
/**
* Implementation of GroupBackend for the LDAP group system.
*/
public class LdapGroupBackend implements GroupBackend {
private static final Logger log = LoggerFactory.getLogger(LdapGroupBackend.class);
private static final String LDAP_NAME = "ldap/";
private static final String GROUPNAME = "groupname";
private final Helper helper;
private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
private final LoadingCache<String, Boolean> existsCache;
private final Provider<CurrentUser> userProvider;
@Inject
LdapGroupBackend(
Helper helper,
@Named(GROUP_CACHE) LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
@Named(GROUP_EXIST_CACHE) LoadingCache<String, Boolean> existsCache,
Provider<CurrentUser> userProvider) {
this.helper = helper;
this.membershipCache = membershipCache;
this.existsCache = existsCache;
this.userProvider = userProvider;
}
private static boolean isLdapUUID(AccountGroup.UUID uuid) {
return uuid.get().startsWith(LDAP_UUID);
}
private static GroupReference groupReference(LdapQuery.Result res)
throws NamingException {
return new GroupReference(
new AccountGroup.UUID(LDAP_UUID + res.getDN()),
LDAP_NAME + cnFor(res.getDN()));
}
private static String cnFor(String dn) {
try {
LdapName name = new LdapName(dn);
if (!name.isEmpty()) {
String cn = name.get(name.size() - 1);
int index = cn.indexOf('=');
if (index >= 0) {
cn = cn.substring(index + 1);
}
return cn;
}
} catch (InvalidNameException e) {
log.warn("Cannot parse LDAP dn for cn", e);
}
return dn;
}
@Override
public boolean handles(AccountGroup.UUID uuid) {
return isLdapUUID(uuid);
}
@Override
public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
if (!handles(uuid)) {
return null;
}
String groupDn = uuid.get().substring(LDAP_UUID.length());
CurrentUser user = userProvider.get();
if (!(user instanceof IdentifiedUser)
|| !membershipsOf((IdentifiedUser) user).contains(uuid)) {
try {
if (!existsCache.get(groupDn)) {
return null;
}
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup group %s in LDAP", groupDn), e);
return null;
}
}
final String name = LDAP_NAME + cnFor(groupDn);
return new GroupDescription.Basic() {
@Override
public AccountGroup.UUID getGroupUUID() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isVisibleToAll() {
return true;
}
};
}
@Override
public Collection<GroupReference> suggest(String name) {
AccountGroup.UUID uuid = new AccountGroup.UUID(name);
if (isLdapUUID(uuid)) {
GroupDescription.Basic g = get(uuid);
if (g == null) {
return Collections.emptySet();
}
return Collections.singleton(GroupReference.forGroup(g));
} else if (name.startsWith(LDAP_NAME)) {
return suggestLdap(name.substring(LDAP_NAME.length()));
}
return Collections.emptySet();
}
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
String id = findId(user.state().getExternalIds());
if (id == null) {
return GroupMembership.EMPTY;
}
try {
return new ListGroupMembership(membershipCache.get(id));
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup membershipsOf %s in LDAP", id), e);
return GroupMembership.EMPTY;
}
}
private static String findId(final Collection<AccountExternalId> ids) {
for (final AccountExternalId i : ids) {
if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
return i.getSchemeRest();
}
}
return null;
}
private Set<GroupReference> suggestLdap(String name) {
if (name.isEmpty()) {
return Collections.emptySet();
}
Set<GroupReference> out = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
try {
DirContext ctx = helper.open();
try {
// Do exact lookups until there are at least 3 characters.
name = Rdn.escapeValue(name) + ((name.length() >= 3) ? "*" : "");
LdapSchema schema = helper.getSchema(ctx);
ParameterizedString filter = ParameterizedString.asis(
schema.groupPattern.replace(GROUPNAME, name).toString());
Set<String> returnAttrs = Collections.<String>emptySet();
Map<String, String> params = Collections.emptyMap();
for (String groupBase : schema.groupBases) {
LdapQuery query = new LdapQuery(
groupBase, schema.groupScope, filter, returnAttrs);
for (LdapQuery.Result res : query.query(ctx, params)) {
out.add(groupReference(res));
}
}
} finally {
try {
ctx.close();
} catch (NamingException e) {
log.warn("Cannot close LDAP query handle", e);
}
}
} catch (NamingException e) {
log.warn("Cannot query LDAP for groups matching requested name", e);
}
return out;
}
}

View File

@@ -17,8 +17,10 @@ package com.google.gerrit.server.auth.ldap;
import static java.util.concurrent.TimeUnit.HOURS;
import com.google.common.base.Optional;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Scopes;
@@ -29,6 +31,8 @@ import java.util.Set;
public class LdapModule extends CacheModule {
static final String USERNAME_CACHE = "ldap_usernames";
static final String GROUP_CACHE = "ldap_groups";
static final String GROUP_EXIST_CACHE = "ldap_group_existence";
@Override
protected void configure() {
@@ -43,7 +47,15 @@ public class LdapModule extends CacheModule {
new TypeLiteral<Optional<Account.Id>>() {})
.loader(LdapRealm.UserLoader.class);
cache(GROUP_EXIST_CACHE,
String.class,
new TypeLiteral<Boolean>() {})
.expireAfterWrite(1, HOURS)
.loader(LdapRealm.ExistenceLoader.class);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
bind(Helper.class);
DynamicSet.bind(binder(), GroupBackend.class).to(LdapGroupBackend.class);
}
}

View File

@@ -20,7 +20,6 @@ import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -28,11 +27,8 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.MaterializedGroupMembership;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.config.AuthConfig;
@@ -48,8 +44,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -58,6 +52,8 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.naming.CompositeName;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
@@ -75,7 +71,6 @@ class LdapRealm implements Realm {
private final Config config;
private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
private final MaterializedGroupMembership.Factory groupMembershipFactory;
@Inject
LdapRealm(
@@ -84,15 +79,13 @@ class LdapRealm implements Realm {
final EmailExpander emailExpander,
@Named(LdapModule.GROUP_CACHE) final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
@Named(LdapModule.USERNAME_CACHE) final LoadingCache<String, Optional<Account.Id>> usernameCache,
@GerritServerConfig final Config config,
final MaterializedGroupMembership.Factory groupMembershipFactory) {
@GerritServerConfig final Config config) {
this.helper = helper;
this.authConfig = authConfig;
this.emailExpander = emailExpander;
this.usernameCache = usernameCache;
this.membershipCache = membershipCache;
this.config = config;
this.groupMembershipFactory = groupMembershipFactory;
this.readOnlyAccountFields = new HashSet<Account.FieldName>();
@@ -265,34 +258,6 @@ class LdapRealm implements Realm {
usernameCache.put(who.getLocalUser(), Optional.of(account.getId()));
}
@Override
public GroupMembership groups(final AccountState who) {
String id = findId(who.getExternalIds());
Set<AccountGroup.UUID> groups;
if (id != null) {
try {
groups = membershipCache.get(id);
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup groups for %s in LDAP", id), e);
groups = Collections.emptySet();
}
} else {
groups = Collections.emptySet();
}
return groupMembershipFactory.create(Iterables.concat(
groups,
who.getInternalGroups()));
}
private static String findId(final Collection<AccountExternalId> ids) {
for (final AccountExternalId i : ids) {
if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
return i.getSchemeRest();
}
}
return null;
}
@Override
public Account.Id lookup(String accountName) {
if (Strings.isNullOrEmpty(accountName)) {
@@ -354,4 +319,33 @@ class LdapRealm implements Realm {
}
}
}
static class ExistenceLoader extends CacheLoader<String, Boolean> {
private final Helper helper;
@Inject
ExistenceLoader(final Helper helper) {
this.helper = helper;
}
@Override
public Boolean load(final String groupDn) throws Exception {
final DirContext ctx = helper.open();
try {
Name compositeGroupName = new CompositeName().add(groupDn);
try {
ctx.getAttributes(compositeGroupName);
return true;
} catch (NamingException e) {
return false;
}
} finally {
try {
ctx.close();
} catch (NamingException e) {
log.warn("Cannot close LDAP query handle", e);
}
}
}
}
}

View File

@@ -38,11 +38,14 @@ import com.google.gerrit.server.account.AccountVisibilityProvider;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.DefaultRealm;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
import com.google.gerrit.server.account.GroupInfoCacheFactory;
import com.google.gerrit.server.account.MaterializedGroupMembership;
import com.google.gerrit.server.account.IncludingGroupMembership;
import com.google.gerrit.server.account.InternalGroupBackend;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.account.UniversalGroupBackend;
import com.google.gerrit.server.auth.ldap.LdapModule;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -130,12 +133,17 @@ public class GerritGlobalModule extends FactoryModule {
factory(InternalUser.Factory.class);
factory(ProjectNode.Factory.class);
factory(ProjectState.Factory.class);
factory(MaterializedGroupMembership.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class)
.toProvider(AccountVisibilityProvider.class)
.in(SINGLETON);
factory(IncludingGroupMembership.Factory.class);
bind(InternalGroupBackend.class).in(SINGLETON);
bind(GroupBackend.class).to(UniversalGroupBackend.class).in(SINGLETON);
DynamicSet.setOf(binder(), GroupBackend.class);
DynamicSet.bind(binder(), GroupBackend.class).to(InternalGroupBackend.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(ToolsCatalog.class);
bind(EventFactory.class);

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -24,9 +24,9 @@ import java.util.Collections;
public class GitReceivePackGroupsProvider extends GroupSetProvider {
@Inject
public GitReceivePackGroupsProvider(GroupCache gc,
public GitReceivePackGroupsProvider(GroupBackend gb,
@GerritServerConfig Config config) {
super(gc, config, "receive", null, "allowGroup");
super(gb, config, "receive", null, "allowGroup");
// If no group was set, default to "registered users"
//

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -25,9 +25,9 @@ import java.util.HashSet;
public class GitUploadPackGroupsProvider extends GroupSetProvider {
@Inject
public GitUploadPackGroupsProvider(GroupCache gc,
public GitUploadPackGroupsProvider(GroupBackend gb,
@GerritServerConfig Config config) {
super(gc, config, "upload", null, "allowGroup");
super(gb, config, "upload", null, "allowGroup");
// If no group was set, default to "registered users" and "anonymous"
//

View File

@@ -15,8 +15,10 @@
package com.google.gerrit.server.config;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,17 +36,17 @@ public abstract class GroupSetProvider implements
protected Set<AccountGroup.UUID> groupIds;
@Inject
protected GroupSetProvider(GroupCache groupCache,
protected GroupSetProvider(GroupBackend groupBackend,
@GerritServerConfig Config config, String section,
String subsection, String name) {
String[] groupNames = config.getStringList(section, subsection, name);
ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
for (String n : groupNames) {
AccountGroup g = groupCache.get(new AccountGroup.NameKey(n));
if (g != null) {
builder.add(g.getGroupUUID());
} else {
GroupReference g = GroupBackends.findBestSuggestion(groupBackend, n);
if (g == null) {
log.warn("Group \"{0}\" not in database, skipping.", n);
} else {
builder.add(g.getUUID());
}
}
groupIds = builder.build();

View File

@@ -14,7 +14,7 @@
package com.google.gerrit.server.config;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
@@ -32,8 +32,8 @@ import org.eclipse.jgit.lib.Config;
*/
public class ProjectOwnerGroupsProvider extends GroupSetProvider {
@Inject
public ProjectOwnerGroupsProvider(GroupCache gc,
public ProjectOwnerGroupsProvider(GroupBackend gb,
@GerritServerConfig final Config config) {
super(gc, config, "repository", "*", "ownerGroup");
super(gb, config, "repository", "*", "ownerGroup");
}
}

View File

@@ -21,6 +21,7 @@ import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -31,8 +32,9 @@ import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.State;
import com.google.gerrit.reviewdb.client.Project.SubmitType;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.server.mail.Address;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -233,13 +235,13 @@ public class ProjectConfig extends VersionedMetaData {
/**
* Check all GroupReferences use current group name, repairing stale ones.
*
* @param groupCache cache to use when looking up group information by UUID.
* @param groupBackend cache to use when looking up group information by UUID.
* @return true if one or more group names was stale.
*/
public boolean updateGroupNames(GroupCache groupCache) {
public boolean updateGroupNames(GroupBackend groupBackend) {
boolean dirty = false;
for (GroupReference ref : groupsByUUID.values()) {
AccountGroup g = groupCache.get(ref.getUUID());
GroupDescription.Basic g = groupBackend.get(ref.getUUID());
if (g != null && !g.getName().equals(ref.getName())) {
dirty = true;
ref.setName(g.getName());

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.mail;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -387,7 +388,8 @@ public abstract class ChangeEmail extends OutgoingEmail {
private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
AccountGroup group = args.groupCache.get(ref.getUUID());
AccountGroup group =
GroupDescriptions.toAccountGroup(args.groupBackend.get(ref.getUUID()));
if (group == null) {
log.warn(String.format(
"Project %s has invalid group %s in notify section %s",

View File

@@ -20,7 +20,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -39,7 +39,7 @@ import javax.annotation.Nullable;
class EmailArguments {
final GitRepositoryManager server;
final ProjectCache projectCache;
final GroupCache groupCache;
final GroupBackend groupBackend;
final AccountCache accountCache;
final PatchListCache patchListCache;
final FromAddressGenerator fromAddressGenerator;
@@ -58,7 +58,7 @@ class EmailArguments {
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
GroupCache groupCache, AccountCache accountCache,
GroupBackend groupBackend, AccountCache accountCache,
PatchListCache patchListCache, FromAddressGenerator fromAddressGenerator,
EmailSender emailSender, PatchSetInfoFactory patchSetInfoFactory,
GenericFactory identifiedUserFactory,
@@ -71,7 +71,7 @@ class EmailArguments {
RuntimeInstance velocityRuntime) {
this.server = server;
this.projectCache = projectCache;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.accountCache = accountCache;
this.patchListCache = patchListCache;
this.fromAddressGenerator = fromAddressGenerator;

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
@@ -26,7 +27,7 @@ import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.ProjectOwnerGroups;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -73,7 +74,7 @@ public class CreateProject {
private final PersonIdent serverIdent;
private CreateProjectArgs createProjectArgs;
private ProjectCache projectCache;
private GroupCache groupCache;
private GroupBackend groupBackend;
private MetaDataUpdate.User metaDataUpdateFactory;
@Inject
@@ -82,8 +83,8 @@ public class CreateProject {
GitReferenceUpdated referenceUpdated,
DynamicSet<NewProjectCreatedListener> createdListener,
ReviewDb db,
@GerritPersonIdent PersonIdent personIdent, final GroupCache groupCache,
final MetaDataUpdate.User metaDataUpdateFactory,
@GerritPersonIdent PersonIdent personIdent, GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
@Assisted CreateProjectArgs createPArgs, ProjectCache pCache) {
this.projectOwnerGroups = pOwnerGroups;
this.currentUser = identifiedUser;
@@ -93,7 +94,7 @@ public class CreateProject {
this.serverIdent = personIdent;
this.createProjectArgs = createPArgs;
this.projectCache = pCache;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
}
@@ -187,9 +188,9 @@ public class CreateProject {
final AccessSection all =
config.getAccessSection(AccessSection.ALL, true);
for (AccountGroup.UUID ownerId : createProjectArgs.ownerIds) {
AccountGroup accountGroup = groupCache.get(ownerId);
if (accountGroup != null) {
GroupReference group = config.resolve(accountGroup);
GroupDescription.Basic g = groupBackend.get(ownerId);
if (g != null) {
GroupReference group = config.resolve(GroupReference.forGroup(g));
all.getPermission(Permission.OWNER, true).add(
new PermissionRule(group));
}

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
@@ -26,7 +27,8 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.CapabilityControl;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
@@ -105,7 +107,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
final ChangeControl.Factory changeControlFactory;
final ChangeControl.GenericFactory changeControlGenericFactory;
final AccountResolver accountResolver;
final GroupCache groupCache;
final GroupBackend groupBackend;
final ApprovalTypes approvalTypes;
final AllProjectsName allProjectsName;
final PatchListCache patchListCache;
@@ -119,7 +121,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
CapabilityControl.Factory capabilityControlFactory,
ChangeControl.Factory changeControlFactory,
ChangeControl.GenericFactory changeControlGenericFactory,
AccountResolver accountResolver, GroupCache groupCache,
AccountResolver accountResolver,
GroupBackend groupBackend,
ApprovalTypes approvalTypes,
AllProjectsName allProjectsName,
PatchListCache patchListCache,
@@ -132,7 +135,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
this.changeControlFactory = changeControlFactory;
this.changeControlGenericFactory = changeControlGenericFactory;
this.accountResolver = accountResolver;
this.groupCache = groupCache;
this.groupBackend = groupBackend;
this.approvalTypes = approvalTypes;
this.allProjectsName = allProjectsName;
this.patchListCache = patchListCache;
@@ -367,18 +370,11 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
// If its not an account, maybe its a group?
//
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
if (g != null) {
return visibleto(new SingleGroupUser(args.capabilityControlFactory,
g.getGroupUUID()));
}
Collection<AccountGroup> matches =
args.groupCache.get(new AccountGroup.ExternalNameKey(who));
if (matches != null && !matches.isEmpty()) {
Collection<GroupReference> suggestions = args.groupBackend.suggest(who);
if (!suggestions.isEmpty()) {
HashSet<AccountGroup.UUID> ids = new HashSet<AccountGroup.UUID>();
for (AccountGroup group : matches) {
ids.add(group.getGroupUUID());
for (GroupReference ref : suggestions) {
ids.add(ref.getUUID());
}
return visibleto(new SingleGroupUser(args.capabilityControlFactory, ids));
}
@@ -410,11 +406,11 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> ownerin(String group)
throws QueryParseException {
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
if (g == null) {
throw error("Group " + group + " not found");
}
return new OwnerinPredicate(args.dbProvider, args.userFactory, g.getGroupUUID());
return new OwnerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
}
@Operator
@@ -431,11 +427,11 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator
public Predicate<ChangeData> reviewerin(String group)
throws QueryParseException {
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
if (g == null) {
throw error("Group " + group + " not found");
}
return new ReviewerinPredicate(args.dbProvider, args.userFactory, g.getGroupUUID());
return new ReviewerinPredicate(args.dbProvider, args.userFactory, g.getUUID());
}
@Operator

View File

@@ -26,7 +26,6 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.ApprovalCategory.Id;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -57,7 +56,7 @@ public class FunctionState {
@Inject
FunctionState(final ApprovalTypes approvalTypes,
final IdentifiedUser.GenericFactory userFactory, final GroupCache egc,
final IdentifiedUser.GenericFactory userFactory,
@Assisted final ChangeControl c, @Assisted final PatchSet.Id psId,
@Assisted final Collection<PatchSetApproval> all) {
this.approvalTypes = approvalTypes;

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.sshd.args4j;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -27,25 +29,25 @@ import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
public class AccountGroupUUIDHandler extends OptionHandler<AccountGroup.UUID> {
private final GroupCache groupCache;
private final GroupBackend groupBackend;
@Inject
public AccountGroupUUIDHandler(final GroupCache groupCache,
public AccountGroupUUIDHandler(final GroupBackend groupBackend,
@Assisted final CmdLineParser parser, @Assisted final OptionDef option,
@Assisted final Setter<AccountGroup.UUID> setter) {
super(parser, option, setter);
this.groupCache = groupCache;
this.groupBackend = groupBackend;
}
@Override
public final int parseArguments(final Parameters params)
throws CmdLineException {
final String n = params.getParameter(0);
final AccountGroup group = groupCache.get(new AccountGroup.NameKey(n));
final GroupReference group = GroupBackends.findBestSuggestion(groupBackend, n);
if (group == null) {
throw new CmdLineException(owner, "Group \"" + n + "\" does not exist");
}
setter.addValue(group.getGroupUUID());
setter.addValue(group.getUUID());
return 1;
}