Add native LDAP support to Gerrit

User account information such as full name and email address
is now obtained from LDAP upon initial account registration.

Group membership is obtained on the fly, but held in a cache
in Gerrit for up to 1 hour.  This permits users who are new
to immediately have access to their LDAP groups, while any
existing users may have to wait up to 1 hour for the cache
to expire and any group modifications to be loaded.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-08-18 18:29:19 -07:00
parent 8cda676408
commit b032a5665d
21 changed files with 753 additions and 215 deletions

View File

@@ -314,6 +314,7 @@ public class Gerrit implements EntryPoint {
} else { } else {
switch (getConfig().getLoginType()) { switch (getConfig().getLoginType()) {
case HTTP: case HTTP:
case HTTP_LDAP:
break; break;
case OPENID: case OPENID:

View File

@@ -21,12 +21,24 @@ public enum LoginType {
/** /**
* Login relies upon the container/web server security. * Login relies upon the container/web server security.
* <p> * <p>
* The container or web server must populate an HTTP header with the some * The container or web server must populate an HTTP header with a unique name
* user token. Gerrit will implicitly trust the value of this header to * for the current user. Gerrit will implicitly trust the value of this header
* supply the unique identity. * to supply the unique identity.
*/ */
HTTP, HTTP,
/**
* Login relies upon the container/web server security, but also uses LDAP.
* <p>
* Like {@link #HTTP}, the container or web server must populate an HTTP
* header with a unique name for the current user. Gerrit will implicitly
* trust the value of this header to supply the unique identity.
* <p>
* In addition to trusting the HTTP headers, Gerrit will obtain basic user
* registration (name and email) from LDAP, and some group memberships.
*/
HTTP_LDAP,
/** Development mode to enable becoming anyone you want. */ /** Development mode to enable becoming anyone you want. */
DEVELOPMENT_BECOME_ANY_ACCOUNT; DEVELOPMENT_BECOME_ANY_ACCOUNT;
} }

View File

@@ -16,7 +16,7 @@ package com.google.gerrit.server;
import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Change; import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.SystemConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -26,17 +26,14 @@ import java.util.Set;
/** An anonymous user who has not yet authenticated. */ /** An anonymous user who has not yet authenticated. */
@Singleton @Singleton
public class AnonymousUser extends CurrentUser { public class AnonymousUser extends CurrentUser {
private final Set<AccountGroup.Id> effectiveGroups;
@Inject @Inject
AnonymousUser(final SystemConfig cfg) { AnonymousUser(final AuthConfig auth) {
super(cfg); super(auth);
effectiveGroups = Collections.singleton(cfg.anonymousGroupId);
} }
@Override @Override
public Set<AccountGroup.Id> getEffectiveGroups() { public Set<AccountGroup.Id> getEffectiveGroups() {
return effectiveGroups; return authConfig.getAnonymousGroups();
} }
@Override @Override

View File

@@ -16,7 +16,7 @@ package com.google.gerrit.server;
import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Change; import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.SystemConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.servlet.RequestScoped; import com.google.inject.servlet.RequestScoped;
import java.util.Set; import java.util.Set;
@@ -30,10 +30,10 @@ import java.util.Set;
* @see IdentifiedUser * @see IdentifiedUser
*/ */
public abstract class CurrentUser { public abstract class CurrentUser {
protected final SystemConfig systemConfig; protected final AuthConfig authConfig;
protected CurrentUser(final SystemConfig cfg) { protected CurrentUser(final AuthConfig authConfig) {
systemConfig = cfg; this.authConfig = authConfig;
} }
/** /**
@@ -54,6 +54,6 @@ public abstract class CurrentUser {
@Deprecated @Deprecated
public final boolean isAdministrator() { public final boolean isAdministrator() {
return getEffectiveGroups().contains(systemConfig.adminGroupId); return getEffectiveGroups().contains(authConfig.getAdministratorsGroup());
} }
} }

View File

@@ -19,9 +19,10 @@ import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Change; import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.StarredChange; import com.google.gerrit.client.reviewdb.StarredChange;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.Nullable; import com.google.gerrit.server.config.Nullable;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -48,18 +49,20 @@ public class IdentifiedUser extends CurrentUser {
/** Create an IdentifiedUser, ignoring any per-request state. */ /** Create an IdentifiedUser, ignoring any per-request state. */
@Singleton @Singleton
public static class GenericFactory { public static class GenericFactory {
private final SystemConfig systemConfig; private final AuthConfig authConfig;
private final AccountCache accountCache; private final AccountCache accountCache;
private final Realm realm;
@Inject @Inject
GenericFactory(final SystemConfig systemConfig, GenericFactory(final AuthConfig authConfig,
final AccountCache accountCache) { final AccountCache accountCache, final Realm realm) {
this.systemConfig = systemConfig; this.authConfig = authConfig;
this.accountCache = accountCache; this.accountCache = accountCache;
this.realm = realm;
} }
public IdentifiedUser create(final Account.Id id) { public IdentifiedUser create(final Account.Id id) {
return new IdentifiedUser(systemConfig, accountCache, null, null, id); return new IdentifiedUser(authConfig, accountCache, realm, null, null, id);
} }
} }
@@ -71,46 +74,55 @@ public class IdentifiedUser extends CurrentUser {
*/ */
@Singleton @Singleton
public static class RequestFactory { public static class RequestFactory {
private final SystemConfig systemConfig; private final AuthConfig authConfig;
private final AccountCache accountCache; private final AccountCache accountCache;
private final Realm realm;
private final Provider<SocketAddress> remotePeerProvider; private final Provider<SocketAddress> remotePeerProvider;
private final Provider<ReviewDb> dbProvider; private final Provider<ReviewDb> dbProvider;
@Inject @Inject
RequestFactory(final SystemConfig systemConfig, RequestFactory(final AuthConfig authConfig,
final AccountCache accountCache, final AccountCache accountCache, final Realm realm,
final @RemotePeer Provider<SocketAddress> remotePeerProvider, final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) { final Provider<ReviewDb> dbProvider) {
this.systemConfig = systemConfig; this.authConfig = authConfig;
this.accountCache = accountCache; this.accountCache = accountCache;
this.realm = realm;
this.remotePeerProvider = remotePeerProvider; this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
} }
public IdentifiedUser create(final Account.Id id) { public IdentifiedUser create(final Account.Id id) {
return new IdentifiedUser(systemConfig, accountCache, remotePeerProvider, return new IdentifiedUser(authConfig, accountCache, realm,
dbProvider, id); remotePeerProvider, dbProvider, id);
} }
} }
private static final Logger log = private static final Logger log =
LoggerFactory.getLogger(IdentifiedUser.class); LoggerFactory.getLogger(IdentifiedUser.class);
private final Realm realm;
private final AccountCache accountCache; private final AccountCache accountCache;
@Nullable @Nullable
private final Provider<SocketAddress> remotePeerProvider; private final Provider<SocketAddress> remotePeerProvider;
@Nullable @Nullable
private final Provider<ReviewDb> dbProvider; private final Provider<ReviewDb> dbProvider;
private final Account.Id accountId; private final Account.Id accountId;
private AccountState state; private AccountState state;
private Set<String> emailAddresses;
private Set<AccountGroup.Id> effectiveGroups;
private Set<Change.Id> starredChanges; private Set<Change.Id> starredChanges;
private IdentifiedUser(final SystemConfig systemConfig, private IdentifiedUser(final AuthConfig authConfig,
final AccountCache accountCache, final AccountCache accountCache, final Realm realm,
@Nullable final Provider<SocketAddress> remotePeerProvider, @Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) { @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
super(systemConfig); super(authConfig);
this.realm = realm;
this.accountCache = accountCache; this.accountCache = accountCache;
this.remotePeerProvider = remotePeerProvider; this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
@@ -134,12 +146,23 @@ public class IdentifiedUser extends CurrentUser {
} }
public Set<String> getEmailAddresses() { public Set<String> getEmailAddresses() {
return state().getEmailAddresses(); if (emailAddresses == null) {
emailAddresses = state().getEmailAddresses();
}
return emailAddresses;
} }
@Override @Override
public Set<AccountGroup.Id> getEffectiveGroups() { public Set<AccountGroup.Id> getEffectiveGroups() {
return state().getEffectiveGroups(); if (effectiveGroups == null) {
if (authConfig.isIdentityTrustable(state().getExternalIds())) {
effectiveGroups = realm.groups(state());
} else {
effectiveGroups = authConfig.getRegisteredGroups();
}
}
return effectiveGroups;
} }
@Override @Override

View File

@@ -1,39 +0,0 @@
// Copyright (C) 2009 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;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
/**
* Access controls for a {@link CurrentUser} within a {@link Project}.
*/
public class ProjectAccess {
interface Factory {
ProjectAccess create(CurrentUser u, ProjectState entry);
}
private final CurrentUser user;
private final ProjectState entry;
@Inject
ProjectAccess(@Assisted final CurrentUser u,
@Assisted final ProjectState e) {
user = u;
entry = e;
}
}

View File

@@ -19,7 +19,6 @@ import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.AccountGroupMember; import com.google.gerrit.client.reviewdb.AccountGroupMember;
import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule; import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.SelfPopulatingCache; import com.google.gerrit.server.cache.SelfPopulatingCache;
@@ -32,9 +31,9 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
/** Caches important (but small) account state to avoid database hits. */ /** Caches important (but small) account state to avoid database hits. */
@@ -55,24 +54,17 @@ public class AccountCache {
} }
private final SchemaFactory<ReviewDb> schema; private final SchemaFactory<ReviewDb> schema;
private final AuthConfig authConfig;
private final SelfPopulatingCache<Account.Id, AccountState> self; private final SelfPopulatingCache<Account.Id, AccountState> self;
private final Set<AccountGroup.Id> registered; private final Set<AccountGroup.Id> registered;
private final Set<AccountGroup.Id> anonymous; private final Set<AccountGroup.Id> anonymous;
@Inject @Inject
AccountCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg, AccountCache(final SchemaFactory<ReviewDb> sf, final AuthConfig auth,
final AuthConfig ac,
@Named(CACHE_NAME) final Cache<Account.Id, AccountState> rawCache) { @Named(CACHE_NAME) final Cache<Account.Id, AccountState> rawCache) {
schema = sf; schema = sf;
authConfig = ac; registered = auth.getAnonymousGroups();
anonymous = auth.getRegisteredGroups();
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>(2);
r.add(cfg.anonymousGroupId);
r.add(cfg.registeredGroupId);
registered = Collections.unmodifiableSet(r);
anonymous = Collections.singleton(cfg.anonymousGroupId);
self = new SelfPopulatingCache<Account.Id, AccountState>(rawCache) { self = new SelfPopulatingCache<Account.Id, AccountState>(rawCache) {
@Override @Override
@@ -97,35 +89,23 @@ public class AccountCache {
return missingAccount(who); return missingAccount(who);
} }
final List<AccountExternalId> ids = final Collection<AccountExternalId> externalIds =
db.accountExternalIds().byAccount(who).toList(); Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
Set<String> emails = new HashSet<String>(); who).toList());
for (AccountExternalId id : ids) {
if (id.getEmailAddress() != null && !id.getEmailAddress().isEmpty()) {
emails.add(id.getEmailAddress());
}
}
Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>(); Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) { for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
actual.add(g.getAccountGroupId()); internalGroups.add(g.getAccountGroupId());
} }
if (actual.isEmpty()) { if (internalGroups.isEmpty()) {
actual = registered; internalGroups = registered;
} else { } else {
actual.addAll(registered); internalGroups.addAll(registered);
actual = Collections.unmodifiableSet(actual); internalGroups = Collections.unmodifiableSet(internalGroups);
} }
final Set<AccountGroup.Id> effective; return new AccountState(account, internalGroups, externalIds);
if (authConfig.isIdentityTrustable(ids)) {
effective = actual;
} else {
effective = registered;
}
return new AccountState(account, actual, effective, emails);
} finally { } finally {
db.close(); db.close();
} }
@@ -133,8 +113,8 @@ public class AccountCache {
private AccountState missingAccount(final Account.Id accountId) { private AccountState missingAccount(final Account.Id accountId) {
final Account account = new Account(accountId); final Account account = new Account(accountId);
final Set<String> emails = Collections.emptySet(); final Collection<AccountExternalId> ids = Collections.emptySet();
return new AccountState(account, anonymous, anonymous, emails); return new AccountState(account, anonymous, ids);
} }
public AccountState get(final Account.Id accountId) { public AccountState get(final Account.Id accountId) {

View File

@@ -34,18 +34,18 @@ public class AccountManager {
private final SchemaFactory<ReviewDb> schema; private final SchemaFactory<ReviewDb> schema;
private final AccountCache byIdCache; private final AccountCache byIdCache;
private final AccountByEmailCache byEmailCache; private final AccountByEmailCache byEmailCache;
private final EmailExpander emailExpander;
private final AuthConfig authConfig; private final AuthConfig authConfig;
private final Realm realm;
@Inject @Inject
AccountManager(final SchemaFactory<ReviewDb> schema, AccountManager(final SchemaFactory<ReviewDb> schema,
final AccountCache byIdCache, final AccountByEmailCache byEmailCache, final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
final EmailExpander emailExpander, final AuthConfig authConfig) { final AuthConfig authConfig, final Realm accountMapper) {
this.schema = schema; this.schema = schema;
this.byIdCache = byIdCache; this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache; this.byEmailCache = byEmailCache;
this.emailExpander = emailExpander;
this.authConfig = authConfig; this.authConfig = authConfig;
this.realm = accountMapper;
} }
/** /**
@@ -74,7 +74,8 @@ public class AccountManager {
* @throws AccountException the account does not exist, and cannot be created, * @throws AccountException the account does not exist, and cannot be created,
* or exists, but cannot be located. * or exists, but cannot be located.
*/ */
public AuthResult authenticate(final AuthRequest who) throws AccountException { public AuthResult authenticate(AuthRequest who) throws AccountException {
who = realm.authenticate(who);
try { try {
final ReviewDb db = schema.open(); final ReviewDb db = schema.open();
try { try {
@@ -180,22 +181,8 @@ public class AccountManager {
final Account account = new Account(newId); final Account account = new Account(newId);
final AccountExternalId extId = createId(newId, who); final AccountExternalId extId = createId(newId, who);
if (who.getLocalUser() != null && who.getEmailAddress() == null) {
// A SCHEMA_GERRIT account was authenticated by an external SSO
// solution. The external identity string actually contains a
// name that we can uniquely refer to the user by, so set
// account information based upon that name.
//
final String user = who.getLocalUser();
if (emailExpander.canExpand(user)) {
extId.setEmailAddress(emailExpander.expand(user));
}
account.setSshUserName(user);
} else if (who.getEmailAddress() != null) {
extId.setEmailAddress(who.getEmailAddress());
}
extId.setLastUsedOn(); extId.setLastUsedOn();
extId.setEmailAddress(who.getEmailAddress());
account.setFullName(who.getDisplayName()); account.setFullName(who.getDisplayName());
account.setPreferredEmail(extId.getEmailAddress()); account.setPreferredEmail(extId.getEmailAddress());

View File

@@ -15,22 +15,23 @@
package com.google.gerrit.server.account; package com.google.gerrit.server.account;
import com.google.gerrit.client.reviewdb.Account; import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.AccountGroup;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class AccountState { public class AccountState {
private final Account account; private final Account account;
private final Set<AccountGroup.Id> actualGroups; private final Set<AccountGroup.Id> internalGroups;
private final Set<AccountGroup.Id> effectiveGroups; private final Collection<AccountExternalId> externalIds;
private final Set<String> emails;
AccountState(final Account a, final Set<AccountGroup.Id> actual, AccountState(final Account account, final Set<AccountGroup.Id> actualGroups,
final Set<AccountGroup.Id> effective, final Set<String> e) { final Collection<AccountExternalId> externalIds) {
this.account = a; this.account = account;
this.actualGroups = actual; this.internalGroups = actualGroups;
this.effectiveGroups = effective; this.externalIds = externalIds;
this.emails = e;
} }
/** Get the cached account metadata. */ /** Get the cached account metadata. */
@@ -48,34 +49,22 @@ public class AccountState {
* validated by Gerrit directly. * validated by Gerrit directly.
*/ */
public Set<String> getEmailAddresses() { public Set<String> getEmailAddresses() {
final Set<String> emails = new HashSet<String>();
for (final AccountExternalId e : externalIds) {
if (e.getEmailAddress() != null && !e.getEmailAddress().isEmpty()) {
emails.add(e.getEmailAddress());
}
}
return emails; return emails;
} }
/** /** The external identities that identify the account holder. */
* Get the set of groups the user has been declared a member of. public Collection<AccountExternalId> getExternalIds() {
* <p> return externalIds;
* The returned set is the complete set of the user's groups. This can be a
* superset of {@link #getEffectiveGroups()} if the user's account is not
* sufficiently trusted to enable additional access.
*
* @return active groups for this user.
*/
public Set<AccountGroup.Id> getActualGroups() {
return actualGroups;
} }
/** /** The set of groups maintained directly within the Gerrit database. */
* Get the set of groups the user is currently a member of. public Set<AccountGroup.Id> getInternalGroups() {
* <p> return internalGroups;
* The returned set may be a subset of {@link #getActualGroups()}. If the
* user's account is currently deemed to be untrusted then the effective group
* set is only the anonymous and registered user groups. To enable additional
* groups (and gain their granted permissions) the user must update their
* account to use only trusted authentication providers.
*
* @return active groups for this user.
*/
public Set<AccountGroup.Id> getEffectiveGroups() {
return effectiveGroups;
} }
} }

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2009 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.client.reviewdb.AccountGroup;
import com.google.inject.Inject;
import java.util.Set;
public final class DefaultRealm implements Realm {
private final EmailExpander emailExpander;
@Inject
DefaultRealm(final EmailExpander emailExpander) {
this.emailExpander = emailExpander;
}
@Override
public AuthRequest authenticate(final AuthRequest who) {
if (who.getEmailAddress() == null && who.getLocalUser() != null
&& emailExpander.canExpand(who.getLocalUser())) {
who.setEmailAddress(emailExpander.expand(who.getLocalUser()));
}
return who;
}
@Override
public Set<AccountGroup.Id> groups(final AccountState who) {
return who.getInternalGroups();
}
}

View File

@@ -16,10 +16,10 @@ package com.google.gerrit.server.account;
import com.google.gerrit.client.reviewdb.AccountGroup; import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.server.cache.Cache; import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule; import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.SelfPopulatingCache; import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory; import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -37,26 +37,29 @@ public class GroupCache {
return new CacheModule() { return new CacheModule() {
@Override @Override
protected void configure() { protected void configure() {
final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> type = final TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>> byId =
new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {}; new TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>>() {};
core(type, CACHE_NAME); core(byId, CACHE_NAME);
bind(GroupCache.class); bind(GroupCache.class);
} }
}; };
} }
private final SchemaFactory<ReviewDb> schema; private final SchemaFactory<ReviewDb> schema;
private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
private final AccountGroup.Id administrators; private final AccountGroup.Id administrators;
private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
private final SelfPopulatingCache<AccountGroup.NameKey, AccountGroup> byName;
@Inject @Inject
GroupCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg, GroupCache(
@Named(CACHE_NAME) final Cache<AccountGroup.Id, AccountGroup> rawCache) { final SchemaFactory<ReviewDb> sf,
final AuthConfig authConfig,
@Named(CACHE_NAME) final Cache<com.google.gwtorm.client.Key<?>, AccountGroup> rawAny) {
schema = sf; schema = sf;
administrators = cfg.adminGroupId; administrators = authConfig.getAdministratorsGroup();
byId = new SelfPopulatingCache<AccountGroup.Id, AccountGroup>(rawCache) { byId =
new SelfPopulatingCache<AccountGroup.Id, AccountGroup>((Cache) rawAny) {
@Override @Override
public AccountGroup createEntry(final AccountGroup.Id key) public AccountGroup createEntry(final AccountGroup.Id key)
throws Exception { throws Exception {
@@ -68,10 +71,16 @@ public class GroupCache {
return missingGroup(key); return missingGroup(key);
} }
}; };
}
public final AccountGroup.Id getAdministrators() { byName =
return administrators; new SelfPopulatingCache<AccountGroup.NameKey, AccountGroup>(
(Cache) rawAny) {
@Override
public AccountGroup createEntry(final AccountGroup.NameKey key)
throws Exception {
return lookup(key);
}
};
} }
private AccountGroup lookup(final AccountGroup.Id groupId) private AccountGroup lookup(final AccountGroup.Id groupId)
@@ -98,28 +107,30 @@ public class GroupCache {
return g; return g;
} }
@SuppressWarnings("unchecked") private AccountGroup lookup(final AccountGroup.NameKey groupName)
public AccountGroup get(final AccountGroup.Id groupId) { throws OrmException {
return byId.get(groupId);
}
public void evict(final AccountGroup.Id groupId) {
byId.remove(groupId);
}
public AccountGroup lookup(final String groupName) throws OrmException {
final ReviewDb db = schema.open(); final ReviewDb db = schema.open();
try { try {
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName); return db.accountGroups().get(groupName);
final AccountGroup group = db.accountGroups().get(nameKey);
if (group != null) {
return group;
} else {
return null;
}
} finally { } finally {
db.close(); db.close();
} }
} }
public AccountGroup get(final AccountGroup.Id groupId) {
return byId.get(groupId);
}
public void evict(final AccountGroup group) {
byId.remove(group.getId());
byName.remove(group.getNameKey());
}
public void evictAfterRename(final AccountGroup.NameKey oldName) {
byName.remove(oldName);
}
public AccountGroup lookup(final String groupName) {
return byName.get(new AccountGroup.NameKey(groupName));
}
} }

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2009 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.client.reviewdb.AccountGroup;
import java.util.Set;
public interface Realm {
public AuthRequest authenticate(AuthRequest who) throws AccountException;
public Set<AccountGroup.Id> groups(AccountState who);
}

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.config; package com.google.gerrit.server.config;
import com.google.gerrit.client.reviewdb.AccountExternalId; import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.LoginType; import com.google.gerrit.client.reviewdb.LoginType;
import com.google.gerrit.client.reviewdb.SystemConfig; import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtjsonrpc.server.SignedToken;
@@ -25,6 +26,9 @@ import com.google.inject.Singleton;
import org.spearce.jgit.lib.Config; import org.spearce.jgit.lib.Config;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/** Authentication related settings from {@code gerrit.config}. */ /** Authentication related settings from {@code gerrit.config}. */
@Singleton @Singleton
@@ -35,6 +39,10 @@ public class AuthConfig {
private final String[] trusted; private final String[] trusted;
private final SignedToken emailReg; private final SignedToken emailReg;
private final AccountGroup.Id administratorGroup;
private final Set<AccountGroup.Id> anonymousGroups;
private final Set<AccountGroup.Id> registeredGroups;
private final boolean allowGoogleAccountUpgrade; private final boolean allowGoogleAccountUpgrade;
@Inject @Inject
@@ -46,6 +54,13 @@ public class AuthConfig {
trusted = toTrusted(cfg); trusted = toTrusted(cfg);
emailReg = new SignedToken(5 * 24 * 60 * 60, s.registerEmailPrivateKey); emailReg = new SignedToken(5 * 24 * 60 * 60, s.registerEmailPrivateKey);
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>(2);
r.add(s.anonymousGroupId);
r.add(s.registeredGroupId);
registeredGroups = Collections.unmodifiableSet(r);
anonymousGroups = Collections.singleton(s.anonymousGroupId);
administratorGroup = s.adminGroupId;
allowGoogleAccountUpgrade = allowGoogleAccountUpgrade =
cfg.getBoolean("auth", "allowgoogleaccountupgrade", false); cfg.getBoolean("auth", "allowgoogleaccountupgrade", false);
} }
@@ -104,10 +119,26 @@ public class AuthConfig {
return allowGoogleAccountUpgrade; return allowGoogleAccountUpgrade;
} }
/** Identity of the magic group with full powers. */
public AccountGroup.Id getAdministratorsGroup() {
return administratorGroup;
}
/** Groups that all users, including anonymous users, belong to. */
public Set<AccountGroup.Id> getAnonymousGroups() {
return anonymousGroups;
}
/** Groups that all users who have created an account belong to. */
public Set<AccountGroup.Id> getRegisteredGroups() {
return registeredGroups;
}
public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) { public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
switch (getLoginType()) { switch (getLoginType()) {
case DEVELOPMENT_BECOME_ANY_ACCOUNT: case DEVELOPMENT_BECOME_ANY_ACCOUNT:
case HTTP: case HTTP:
case HTTP_LDAP:
// Its safe to assume yes for an HTTP authentication type, as the // Its safe to assume yes for an HTTP authentication type, as the
// only way in is through some external system that the admin trusts // only way in is through some external system that the admin trusts
// //

View File

@@ -18,6 +18,7 @@ import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION; import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.client.data.ApprovalTypes; import com.google.gerrit.client.data.ApprovalTypes;
import com.google.gerrit.client.reviewdb.LoginType;
import com.google.gerrit.git.ChangeMergeQueue; import com.google.gerrit.git.ChangeMergeQueue;
import com.google.gerrit.git.MergeOp; import com.google.gerrit.git.MergeOp;
import com.google.gerrit.git.MergeQueue; import com.google.gerrit.git.MergeQueue;
@@ -35,9 +36,12 @@ import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.account.AccountByEmailCache; import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountInfoCacheFactory; import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.account.DefaultRealm;
import com.google.gerrit.server.account.EmailExpander; import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.CachePool; import com.google.gerrit.server.cache.CachePool;
import com.google.gerrit.server.ldap.LdapModule;
import com.google.gerrit.server.mail.AbandonedSender; import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.AddReviewerSender; import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.mail.CommentSender; import com.google.gerrit.server.mail.CommentSender;
@@ -54,9 +58,12 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.ssh.SshKeyCache; import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gerrit.server.workflow.FunctionState; import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Module; import com.google.inject.Module;
import org.spearce.jgit.lib.Config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -70,12 +77,30 @@ public class GerritGlobalModule extends FactoryModule {
public static Injector createInjector(final Injector db) { public static Injector createInjector(final Injector db) {
final Injector cfg = db.createChildInjector(new GerritConfigModule()); final Injector cfg = db.createChildInjector(new GerritConfigModule());
final List<Module> modules = new ArrayList<Module>(); final List<Module> modules = new ArrayList<Module>();
modules.add(new GerritGlobalModule()); modules.add(cfg.getInstance(GerritGlobalModule.class));
return cfg.createChildInjector(modules); return cfg.createChildInjector(modules);
} }
private final LoginType loginType;
@Inject
GerritGlobalModule(final AuthConfig authConfig,
@GerritServerConfig final Config config) {
loginType = authConfig.getLoginType();
}
@Override @Override
protected void configure() { protected void configure() {
switch (loginType) {
case HTTP_LDAP:
install(new LdapModule());
break;
default:
bind(Realm.class).to(DefaultRealm.class);
break;
}
bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in( bind(ApprovalTypes.class).toProvider(ApprovalTypesProvider.class).in(
SINGLETON); SINGLETON);
bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in( bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in(

View File

@@ -70,6 +70,7 @@ class WebModule extends FactoryModule {
break; break;
case HTTP: case HTTP:
case HTTP_LDAP:
install(new HttpAuthModule()); install(new HttpAuthModule());
break; break;

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2009 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.ldap;
import static java.util.concurrent.TimeUnit.HOURS;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import java.util.Set;
public class LdapModule extends CacheModule {
static final String GROUP_CACHE = "ldap_groups";
@Override
protected void configure() {
final TypeLiteral<Cache<String, Set<AccountGroup.Id>>> type =
new TypeLiteral<Cache<String, Set<AccountGroup.Id>>>() {};
core(type, GROUP_CACHE).timeToIdle(1, HOURS).timeToLive(1, HOURS);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
}
}

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2009 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.ldap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
/** Supports issuing parameterized queries against an LDAP data source. */
class LdapQuery {
private final String base;
private final String pattern;
private final String[] patternArgs;
private final String[] returnAttributes;
LdapQuery(final String base, final String pattern,
final Set<String> returnAttributes) {
this.base = base;
final StringBuilder p = new StringBuilder();
final List<String> a = new ArrayList<String>(4);
int i = 0;
while (i < pattern.length()) {
final int b = pattern.indexOf("${", i);
if (b < 0) {
break;
}
final int e = pattern.indexOf("}", b + 2);
if (e < 0) {
break;
}
p.append(pattern.substring(i, b));
p.append("{" + a.size() + "}");
a.add(pattern.substring(b + 2, e));
i = e + 1;
}
if (i < pattern.length()) {
p.append(pattern.substring(i));
}
this.pattern = p.toString();
this.patternArgs = new String[a.size()];
a.toArray(this.patternArgs);
this.returnAttributes = new String[returnAttributes.size()];
returnAttributes.toArray(this.returnAttributes);
}
String[] getParameters() {
return patternArgs;
}
List<Result> query(final DirContext ctx, final Map<String, String> params)
throws NamingException {
final SearchControls sc = new SearchControls();
final NamingEnumeration<SearchResult> res;
sc.setSearchScope(SearchControls.ONELEVEL_SCOPE);
sc.setReturningAttributes(returnAttributes);
res = ctx.search(base, pattern, bind(params), sc);
try {
final List<Result> r = new ArrayList<Result>();
while (res.hasMore()) {
r.add(new Result(res.next()));
}
return r;
} finally {
res.close();
}
}
private String[] bind(final Map<String, String> params) {
final String[] r = new String[patternArgs.length];
for (int i = 0; i < r.length; i++) {
r[i] = params.get(patternArgs[i]);
if (r[i] == null) {
r[i] = "";
}
}
return r;
}
class Result {
private final Map<String, String> atts = new HashMap<String, String>();
Result(final SearchResult sr) throws NamingException {
for (final String attName : returnAttributes) {
final Attribute a = sr.getAttributes().get(attName);
if (a != null && a.size() > 0) {
atts.put(attName, String.valueOf(a.get(0)));
}
}
atts.put("dn", sr.getNameInNamespace());
}
String get(final String attName) {
return atts.get(attName);
}
}
}

View File

@@ -0,0 +1,285 @@
// Copyright (C) 2009 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.ldap;
import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountGroup;
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.GroupCache;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.Config;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
@Singleton
class LdapRealm implements Realm {
private static final Logger log = LoggerFactory.getLogger(LdapRealm.class);
private static final String LDAP = "com.sun.jndi.ldap.LdapCtxFactory";
private static final String USERNAME = "username";
private final String server;
private final String username;
private final String password;
private final EmailExpander emailExpander;
private final String accountDisplayName;
private final String accountEmailAddress;
private final LdapQuery accountQuery;
private final GroupCache groupCache;
private final String groupName;
private boolean groupNeedsAccount;
private final LdapQuery groupMemberQuery;
private final SelfPopulatingCache<String, Set<AccountGroup.Id>> membershipCache;
@Inject
LdapRealm(
final GroupCache groupCache,
final EmailExpander emailExpander,
@Named(LdapModule.GROUP_CACHE) final Cache<String, Set<AccountGroup.Id>> rawGroup,
@GerritServerConfig final Config config) {
this.emailExpander = emailExpander;
this.groupCache = groupCache;
this.server = required(config, "server");
this.username = optional(config, "username");
this.password = optional(config, "password");
// Group query
//
final Set<String> groupAtts = new HashSet<String>();
groupName = reqdef(config, "groupName", "cn");
groupAtts.add(groupName);
final String groupBase = required(config, "groupBase");
final String groupMemberPattern =
reqdef(config, "groupMemberPattern", "(memberUid=${username})");
groupMemberQuery = new LdapQuery(groupBase, groupMemberPattern, groupAtts);
if (groupMemberQuery.getParameters().length == 0) {
throw new IllegalArgumentException(
"No variables in ldap.groupMemberPattern");
}
membershipCache =
new SelfPopulatingCache<String, Set<AccountGroup.Id>>(rawGroup) {
@Override
public Set<AccountGroup.Id> createEntry(final String username)
throws Exception {
return queryForGroups(username);
}
@Override
protected Set<AccountGroup.Id> missing(final String key) {
return Collections.emptySet();
}
};
// Account query
//
final Set<String> accountAtts = new HashSet<String>();
accountDisplayName = optdef(config, "accountDisplayName", "displayName");
if (accountDisplayName != null) {
accountAtts.add(accountDisplayName);
}
accountEmailAddress = optdef(config, "accountEmailAddress", "mail");
if (accountEmailAddress != null) {
accountAtts.add(accountEmailAddress);
}
for (final String name : groupMemberQuery.getParameters()) {
if (!USERNAME.equals(name)) {
groupNeedsAccount = true;
accountAtts.add(name);
}
}
final String accountBase = required(config, "accountBase");
final String accountPattern =
reqdef(config, "accountPattern", "(uid=${username})");
accountQuery = new LdapQuery(accountBase, accountPattern, accountAtts);
if (accountQuery.getParameters().length == 0) {
throw new IllegalArgumentException("No variables in ldap.accountPattern");
}
}
private static String optional(final Config config, final String name) {
return config.getString("ldap", null, name);
}
private static String required(final Config config, final String name) {
final String v = optional(config, name);
if (v == null || "".equals(v)) {
throw new IllegalArgumentException("No ldap." + name + " configured");
}
return v;
}
private static String optdef(final Config c, final String n, final String d) {
final String v = c.getString("ldap", null, n);
if (v == null) {
return d;
} else if ("".equals(v)) {
return null;
} else {
return v;
}
}
private static String reqdef(final Config c, final String n, final String d) {
final String v = optdef(c, n, d);
if (v == null) {
throw new IllegalArgumentException("No ldap." + n + " configured");
}
return v;
}
public AuthRequest authenticate(final AuthRequest who)
throws AccountException {
final String username = who.getLocalUser();
try {
final DirContext ctx = open();
try {
final LdapQuery.Result m = findAccount(ctx, username);
who.setDisplayName(m.get(accountDisplayName));
if (accountEmailAddress != null) {
who.setEmailAddress(m.get(accountEmailAddress));
} else if (emailExpander.canExpand(username)) {
// If LDAP cannot give us a valid email address for this user
// try expanding it through the older email expander code which
// assumes a user name within a domain.
//
who.setEmailAddress(emailExpander.expand(username));
}
return who;
} finally {
try {
ctx.close();
} catch (NamingException e) {
log.warn("Cannot close LDAP query handle", e);
}
}
} catch (NamingException e) {
throw new AccountException("Cannot query LDAP for account", e);
}
}
@Override
public Set<AccountGroup.Id> groups(final AccountState who) {
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>();
r.addAll(membershipCache.get(findId(who.getExternalIds())));
r.addAll(who.getInternalGroups());
return r;
}
private Set<AccountGroup.Id> queryForGroups(final String username)
throws NamingException, AccountException {
final DirContext ctx = open();
try {
final HashMap<String, String> params = new HashMap<String, String>();
params.put(USERNAME, username);
if (groupNeedsAccount) {
final LdapQuery.Result m = findAccount(ctx, username);
for (final String name : groupMemberQuery.getParameters()) {
params.put(name, m.get(name));
}
}
final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
final String name = r.get(groupName);
final AccountGroup group = groupCache.lookup(name);
if (group != null && isLdapGroup(group)) {
actual.add(group.getId());
}
}
if (actual.isEmpty()) {
return Collections.emptySet();
} else {
return Collections.unmodifiableSet(actual);
}
} finally {
try {
ctx.close();
} catch (NamingException e) {
log.warn("Cannot close LDAP query handle", e);
}
}
}
private boolean isLdapGroup(final AccountGroup group) {
return group.isAutomaticMembership();
}
private static String findId(final Collection<AccountExternalId> ids) {
for (final AccountExternalId i : ids) {
if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
return i.getSchemeRest(AccountExternalId.SCHEME_GERRIT);
}
}
return null;
}
private DirContext open() throws NamingException {
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP);
env.put(Context.PROVIDER_URL, server);
if (username != null) {
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
}
return new InitialDirContext(env);
}
private LdapQuery.Result findAccount(final DirContext ctx,
final String username) throws NamingException, AccountException {
final HashMap<String, String> params = new HashMap<String, String>();
params.put(USERNAME, username);
final List<LdapQuery.Result> res = accountQuery.query(ctx, params);
switch (res.size()) {
case 0:
throw new AccountException("No such user:" + username);
case 1:
return res.get(0);
default:
throw new AccountException("Duplicate users: " + username);
}
}
}

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.AccountGroupMember; import com.google.gerrit.client.reviewdb.AccountGroupMember;
import com.google.gerrit.client.reviewdb.AccountGroupMemberAudit; import com.google.gerrit.client.reviewdb.AccountGroupMemberAudit;
import com.google.gerrit.client.reviewdb.ReviewDb; import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.AccountGroup.NameKey;
import com.google.gerrit.client.rpc.NameAlreadyUsedException; import com.google.gerrit.client.rpc.NameAlreadyUsedException;
import com.google.gerrit.client.rpc.NoSuchAccountException; import com.google.gerrit.client.rpc.NoSuchAccountException;
import com.google.gerrit.client.rpc.NoSuchEntityException; import com.google.gerrit.client.rpc.NoSuchEntityException;
@@ -154,7 +155,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
assertAmGroupOwner(db, group); assertAmGroupOwner(db, group);
group.setDescription(description); group.setDescription(description);
db.accountGroups().update(Collections.singleton(group)); db.accountGroups().update(Collections.singleton(group));
groupCache.evict(groupId); groupCache.evict(group);
return VoidResult.INSTANCE; return VoidResult.INSTANCE;
} }
}); });
@@ -175,7 +176,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
group.setOwnerGroupId(owner.getId()); group.setOwnerGroupId(owner.getId());
db.accountGroups().update(Collections.singleton(group)); db.accountGroups().update(Collections.singleton(group));
groupCache.evict(groupId); groupCache.evict(group);
return VoidResult.INSTANCE; return VoidResult.INSTANCE;
} }
}); });
@@ -188,14 +189,16 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
final AccountGroup group = db.accountGroups().get(groupId); final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group); assertAmGroupOwner(db, group);
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newName); final AccountGroup.NameKey oldKey = group.getNameKey();
if (!nameKey.equals(group.getNameKey())) { final AccountGroup.NameKey newKey = new AccountGroup.NameKey(newName);
if (db.accountGroups().get(nameKey) != null) { if (!newKey.equals(oldKey)) {
if (db.accountGroups().get(newKey) != null) {
throw new Failure(new NameAlreadyUsedException()); throw new Failure(new NameAlreadyUsedException());
} }
group.setNameKey(nameKey); group.setNameKey(newKey);
db.accountGroups().update(Collections.singleton(group)); db.accountGroups().update(Collections.singleton(group));
groupCache.evict(groupId); groupCache.evict(group);
groupCache.evictAfterRename(oldKey);
} }
return VoidResult.INSTANCE; return VoidResult.INSTANCE;
} }

View File

@@ -24,6 +24,7 @@ import com.google.gerrit.client.reviewdb.Project.SubmitType;
import com.google.gerrit.git.ReplicationQueue; import com.google.gerrit.git.ReplicationQueue;
import com.google.gerrit.server.GerritServer; import com.google.gerrit.server.GerritServer;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.ssh.AdminCommand; import com.google.gerrit.server.ssh.AdminCommand;
import com.google.gerrit.server.ssh.BaseCommand; import com.google.gerrit.server.ssh.BaseCommand;
import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.OrmException;
@@ -66,6 +67,9 @@ final class AdminCreateProject extends BaseCommand {
@Inject @Inject
private GroupCache groupCache; private GroupCache groupCache;
@Inject
private AuthConfig authConfig;
@Inject @Inject
private ReplicationQueue rq; private ReplicationQueue rq;
@@ -167,7 +171,7 @@ final class AdminCreateProject extends BaseCommand {
} }
if (ownerName == null) { if (ownerName == null) {
ownerId = groupCache.getAdministrators(); ownerId = authConfig.getAdministratorsGroup();
} else { } else {
AccountGroup ownerGroup = groupCache.lookup(ownerName); AccountGroup ownerGroup = groupCache.lookup(ownerName);
if (ownerGroup == null) { if (ownerGroup == null) {

View File

@@ -26,7 +26,7 @@ import com.google.gerrit.client.reviewdb.PatchSetApproval;
import com.google.gerrit.client.reviewdb.Project; import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight; import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ApprovalCategory.Id; import com.google.gerrit.client.reviewdb.ApprovalCategory.Id;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
@@ -50,7 +50,7 @@ public class FunctionState {
} }
private final ApprovalTypes approvalTypes; private final ApprovalTypes approvalTypes;
private final AccountCache accountCache; private final IdentifiedUser.GenericFactory userFactory;
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final Map<ApprovalCategory.Id, Collection<PatchSetApproval>> approvals = private final Map<ApprovalCategory.Id, Collection<PatchSetApproval>> approvals =
@@ -67,12 +67,12 @@ public class FunctionState {
@Inject @Inject
FunctionState(final ApprovalTypes approvalTypes, final ProjectCache pc, FunctionState(final ApprovalTypes approvalTypes, final ProjectCache pc,
final AccountCache ac, final GroupCache egc, @Assisted final Change c, final IdentifiedUser.GenericFactory userFactory, final GroupCache egc,
@Assisted final PatchSet.Id psId, @Assisted final Change c, @Assisted final PatchSet.Id psId,
@Assisted final Collection<PatchSetApproval> all) { @Assisted final Collection<PatchSetApproval> all) {
this.approvalTypes = approvalTypes; this.approvalTypes = approvalTypes;
this.userFactory = userFactory;
projectCache = pc; projectCache = pc;
accountCache = ac;
change = c; change = c;
project = projectCache.get(change.getDest().getParentKey()); project = projectCache.get(change.getDest().getParentKey());
@@ -231,7 +231,7 @@ public class FunctionState {
for (final ProjectRight r : getAllRights(a.getCategoryId())) { for (final ProjectRight r : getAllRights(a.getCategoryId())) {
final Account.Id who = a.getAccountId(); final Account.Id who = a.getAccountId();
final AccountGroup.Id grp = r.getAccountGroupId(); final AccountGroup.Id grp = r.getAccountGroupId();
if (accountCache.get(who).getEffectiveGroups().contains(grp)) { if (userFactory.create(who).getEffectiveGroups().contains(grp)) {
minAllowed = (short) Math.min(minAllowed, r.getMinValue()); minAllowed = (short) Math.min(minAllowed, r.getMinValue());
maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue()); maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
} }