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:
@@ -314,6 +314,7 @@ public class Gerrit implements EntryPoint {
|
||||
} else {
|
||||
switch (getConfig().getLoginType()) {
|
||||
case HTTP:
|
||||
case HTTP_LDAP:
|
||||
break;
|
||||
|
||||
case OPENID:
|
||||
|
||||
@@ -21,12 +21,24 @@ public enum LoginType {
|
||||
/**
|
||||
* Login relies upon the container/web server security.
|
||||
* <p>
|
||||
* The container or web server must populate an HTTP header with the some
|
||||
* user token. Gerrit will implicitly trust the value of this header to
|
||||
* supply the unique identity.
|
||||
* 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.
|
||||
*/
|
||||
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_BECOME_ANY_ACCOUNT;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
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.Singleton;
|
||||
|
||||
@@ -26,17 +26,14 @@ import java.util.Set;
|
||||
/** An anonymous user who has not yet authenticated. */
|
||||
@Singleton
|
||||
public class AnonymousUser extends CurrentUser {
|
||||
private final Set<AccountGroup.Id> effectiveGroups;
|
||||
|
||||
@Inject
|
||||
AnonymousUser(final SystemConfig cfg) {
|
||||
super(cfg);
|
||||
effectiveGroups = Collections.singleton(cfg.anonymousGroupId);
|
||||
AnonymousUser(final AuthConfig auth) {
|
||||
super(auth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AccountGroup.Id> getEffectiveGroups() {
|
||||
return effectiveGroups;
|
||||
return authConfig.getAnonymousGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,7 @@ package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
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 java.util.Set;
|
||||
@@ -30,10 +30,10 @@ import java.util.Set;
|
||||
* @see IdentifiedUser
|
||||
*/
|
||||
public abstract class CurrentUser {
|
||||
protected final SystemConfig systemConfig;
|
||||
protected final AuthConfig authConfig;
|
||||
|
||||
protected CurrentUser(final SystemConfig cfg) {
|
||||
systemConfig = cfg;
|
||||
protected CurrentUser(final AuthConfig authConfig) {
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +54,6 @@ public abstract class CurrentUser {
|
||||
|
||||
@Deprecated
|
||||
public final boolean isAdministrator() {
|
||||
return getEffectiveGroups().contains(systemConfig.adminGroupId);
|
||||
return getEffectiveGroups().contains(authConfig.getAdministratorsGroup());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
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.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.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
@@ -48,18 +49,20 @@ public class IdentifiedUser extends CurrentUser {
|
||||
/** Create an IdentifiedUser, ignoring any per-request state. */
|
||||
@Singleton
|
||||
public static class GenericFactory {
|
||||
private final SystemConfig systemConfig;
|
||||
private final AuthConfig authConfig;
|
||||
private final AccountCache accountCache;
|
||||
private final Realm realm;
|
||||
|
||||
@Inject
|
||||
GenericFactory(final SystemConfig systemConfig,
|
||||
final AccountCache accountCache) {
|
||||
this.systemConfig = systemConfig;
|
||||
GenericFactory(final AuthConfig authConfig,
|
||||
final AccountCache accountCache, final Realm realm) {
|
||||
this.authConfig = authConfig;
|
||||
this.accountCache = accountCache;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
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
|
||||
public static class RequestFactory {
|
||||
private final SystemConfig systemConfig;
|
||||
private final AuthConfig authConfig;
|
||||
private final AccountCache accountCache;
|
||||
private final Realm realm;
|
||||
private final Provider<SocketAddress> remotePeerProvider;
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
|
||||
@Inject
|
||||
RequestFactory(final SystemConfig systemConfig,
|
||||
final AccountCache accountCache,
|
||||
RequestFactory(final AuthConfig authConfig,
|
||||
final AccountCache accountCache, final Realm realm,
|
||||
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
|
||||
final Provider<ReviewDb> dbProvider) {
|
||||
this.systemConfig = systemConfig;
|
||||
this.authConfig = authConfig;
|
||||
this.accountCache = accountCache;
|
||||
this.realm = realm;
|
||||
this.remotePeerProvider = remotePeerProvider;
|
||||
this.dbProvider = dbProvider;
|
||||
}
|
||||
|
||||
public IdentifiedUser create(final Account.Id id) {
|
||||
return new IdentifiedUser(systemConfig, accountCache, remotePeerProvider,
|
||||
dbProvider, id);
|
||||
return new IdentifiedUser(authConfig, accountCache, realm,
|
||||
remotePeerProvider, dbProvider, id);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(IdentifiedUser.class);
|
||||
|
||||
private final Realm realm;
|
||||
private final AccountCache accountCache;
|
||||
|
||||
@Nullable
|
||||
private final Provider<SocketAddress> remotePeerProvider;
|
||||
|
||||
@Nullable
|
||||
private final Provider<ReviewDb> dbProvider;
|
||||
|
||||
private final Account.Id accountId;
|
||||
|
||||
private AccountState state;
|
||||
private Set<String> emailAddresses;
|
||||
private Set<AccountGroup.Id> effectiveGroups;
|
||||
private Set<Change.Id> starredChanges;
|
||||
|
||||
private IdentifiedUser(final SystemConfig systemConfig,
|
||||
final AccountCache accountCache,
|
||||
private IdentifiedUser(final AuthConfig authConfig,
|
||||
final AccountCache accountCache, final Realm realm,
|
||||
@Nullable final Provider<SocketAddress> remotePeerProvider,
|
||||
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
|
||||
super(systemConfig);
|
||||
super(authConfig);
|
||||
this.realm = realm;
|
||||
this.accountCache = accountCache;
|
||||
this.remotePeerProvider = remotePeerProvider;
|
||||
this.dbProvider = dbProvider;
|
||||
@@ -134,12 +146,23 @@ public class IdentifiedUser extends CurrentUser {
|
||||
}
|
||||
|
||||
public Set<String> getEmailAddresses() {
|
||||
return state().getEmailAddresses();
|
||||
if (emailAddresses == null) {
|
||||
emailAddresses = state().getEmailAddresses();
|
||||
}
|
||||
return emailAddresses;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import com.google.gerrit.client.reviewdb.AccountExternalId;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroupMember;
|
||||
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.CacheModule;
|
||||
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.name.Named;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Caches important (but small) account state to avoid database hits. */
|
||||
@@ -55,24 +54,17 @@ public class AccountCache {
|
||||
}
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final AuthConfig authConfig;
|
||||
private final SelfPopulatingCache<Account.Id, AccountState> self;
|
||||
|
||||
private final Set<AccountGroup.Id> registered;
|
||||
private final Set<AccountGroup.Id> anonymous;
|
||||
|
||||
@Inject
|
||||
AccountCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg,
|
||||
final AuthConfig ac,
|
||||
AccountCache(final SchemaFactory<ReviewDb> sf, final AuthConfig auth,
|
||||
@Named(CACHE_NAME) final Cache<Account.Id, AccountState> rawCache) {
|
||||
schema = sf;
|
||||
authConfig = ac;
|
||||
|
||||
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);
|
||||
registered = auth.getAnonymousGroups();
|
||||
anonymous = auth.getRegisteredGroups();
|
||||
|
||||
self = new SelfPopulatingCache<Account.Id, AccountState>(rawCache) {
|
||||
@Override
|
||||
@@ -97,35 +89,23 @@ public class AccountCache {
|
||||
return missingAccount(who);
|
||||
}
|
||||
|
||||
final List<AccountExternalId> ids =
|
||||
db.accountExternalIds().byAccount(who).toList();
|
||||
Set<String> emails = new HashSet<String>();
|
||||
for (AccountExternalId id : ids) {
|
||||
if (id.getEmailAddress() != null && !id.getEmailAddress().isEmpty()) {
|
||||
emails.add(id.getEmailAddress());
|
||||
}
|
||||
}
|
||||
final Collection<AccountExternalId> externalIds =
|
||||
Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
|
||||
who).toList());
|
||||
|
||||
Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
|
||||
Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
|
||||
for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
|
||||
actual.add(g.getAccountGroupId());
|
||||
internalGroups.add(g.getAccountGroupId());
|
||||
}
|
||||
|
||||
if (actual.isEmpty()) {
|
||||
actual = registered;
|
||||
if (internalGroups.isEmpty()) {
|
||||
internalGroups = registered;
|
||||
} else {
|
||||
actual.addAll(registered);
|
||||
actual = Collections.unmodifiableSet(actual);
|
||||
internalGroups.addAll(registered);
|
||||
internalGroups = Collections.unmodifiableSet(internalGroups);
|
||||
}
|
||||
|
||||
final Set<AccountGroup.Id> effective;
|
||||
if (authConfig.isIdentityTrustable(ids)) {
|
||||
effective = actual;
|
||||
} else {
|
||||
effective = registered;
|
||||
}
|
||||
|
||||
return new AccountState(account, actual, effective, emails);
|
||||
return new AccountState(account, internalGroups, externalIds);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
@@ -133,8 +113,8 @@ public class AccountCache {
|
||||
|
||||
private AccountState missingAccount(final Account.Id accountId) {
|
||||
final Account account = new Account(accountId);
|
||||
final Set<String> emails = Collections.emptySet();
|
||||
return new AccountState(account, anonymous, anonymous, emails);
|
||||
final Collection<AccountExternalId> ids = Collections.emptySet();
|
||||
return new AccountState(account, anonymous, ids);
|
||||
}
|
||||
|
||||
public AccountState get(final Account.Id accountId) {
|
||||
|
||||
@@ -34,18 +34,18 @@ public class AccountManager {
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final AccountCache byIdCache;
|
||||
private final AccountByEmailCache byEmailCache;
|
||||
private final EmailExpander emailExpander;
|
||||
private final AuthConfig authConfig;
|
||||
private final Realm realm;
|
||||
|
||||
@Inject
|
||||
AccountManager(final SchemaFactory<ReviewDb> schema,
|
||||
final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
|
||||
final EmailExpander emailExpander, final AuthConfig authConfig) {
|
||||
final AuthConfig authConfig, final Realm accountMapper) {
|
||||
this.schema = schema;
|
||||
this.byIdCache = byIdCache;
|
||||
this.byEmailCache = byEmailCache;
|
||||
this.emailExpander = emailExpander;
|
||||
this.authConfig = authConfig;
|
||||
this.realm = accountMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +74,8 @@ public class AccountManager {
|
||||
* @throws AccountException the account does not exist, and cannot be created,
|
||||
* 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 {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
@@ -180,22 +181,8 @@ public class AccountManager {
|
||||
final Account account = new Account(newId);
|
||||
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.setEmailAddress(who.getEmailAddress());
|
||||
account.setFullName(who.getDisplayName());
|
||||
account.setPreferredEmail(extId.getEmailAddress());
|
||||
|
||||
|
||||
@@ -15,22 +15,23 @@
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountExternalId;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class AccountState {
|
||||
private final Account account;
|
||||
private final Set<AccountGroup.Id> actualGroups;
|
||||
private final Set<AccountGroup.Id> effectiveGroups;
|
||||
private final Set<String> emails;
|
||||
private final Set<AccountGroup.Id> internalGroups;
|
||||
private final Collection<AccountExternalId> externalIds;
|
||||
|
||||
AccountState(final Account a, final Set<AccountGroup.Id> actual,
|
||||
final Set<AccountGroup.Id> effective, final Set<String> e) {
|
||||
this.account = a;
|
||||
this.actualGroups = actual;
|
||||
this.effectiveGroups = effective;
|
||||
this.emails = e;
|
||||
AccountState(final Account account, final Set<AccountGroup.Id> actualGroups,
|
||||
final Collection<AccountExternalId> externalIds) {
|
||||
this.account = account;
|
||||
this.internalGroups = actualGroups;
|
||||
this.externalIds = externalIds;
|
||||
}
|
||||
|
||||
/** Get the cached account metadata. */
|
||||
@@ -48,34 +49,22 @@ public class AccountState {
|
||||
* validated by Gerrit directly.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of groups the user has been declared a member of.
|
||||
* <p>
|
||||
* 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 external identities that identify the account holder. */
|
||||
public Collection<AccountExternalId> getExternalIds() {
|
||||
return externalIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of groups the user is currently a member of.
|
||||
* <p>
|
||||
* 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;
|
||||
/** The set of groups maintained directly within the Gerrit database. */
|
||||
public Set<AccountGroup.Id> getInternalGroups() {
|
||||
return internalGroups;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,10 @@ package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
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.CacheModule;
|
||||
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.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
@@ -37,26 +37,29 @@ public class GroupCache {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> type =
|
||||
new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
final TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>> byId =
|
||||
new TypeLiteral<Cache<com.google.gwtorm.client.Key<?>, AccountGroup>>() {};
|
||||
core(byId, CACHE_NAME);
|
||||
bind(GroupCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
|
||||
|
||||
private final AccountGroup.Id administrators;
|
||||
private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
|
||||
private final SelfPopulatingCache<AccountGroup.NameKey, AccountGroup> byName;
|
||||
|
||||
@Inject
|
||||
GroupCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg,
|
||||
@Named(CACHE_NAME) final Cache<AccountGroup.Id, AccountGroup> rawCache) {
|
||||
GroupCache(
|
||||
final SchemaFactory<ReviewDb> sf,
|
||||
final AuthConfig authConfig,
|
||||
@Named(CACHE_NAME) final Cache<com.google.gwtorm.client.Key<?>, AccountGroup> rawAny) {
|
||||
schema = sf;
|
||||
administrators = cfg.adminGroupId;
|
||||
administrators = authConfig.getAdministratorsGroup();
|
||||
|
||||
byId = new SelfPopulatingCache<AccountGroup.Id, AccountGroup>(rawCache) {
|
||||
byId =
|
||||
new SelfPopulatingCache<AccountGroup.Id, AccountGroup>((Cache) rawAny) {
|
||||
@Override
|
||||
public AccountGroup createEntry(final AccountGroup.Id key)
|
||||
throws Exception {
|
||||
@@ -68,10 +71,16 @@ public class GroupCache {
|
||||
return missingGroup(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final AccountGroup.Id getAdministrators() {
|
||||
return administrators;
|
||||
byName =
|
||||
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)
|
||||
@@ -98,28 +107,30 @@ public class GroupCache {
|
||||
return g;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AccountGroup get(final AccountGroup.Id groupId) {
|
||||
return byId.get(groupId);
|
||||
}
|
||||
|
||||
public void evict(final AccountGroup.Id groupId) {
|
||||
byId.remove(groupId);
|
||||
}
|
||||
|
||||
public AccountGroup lookup(final String groupName) throws OrmException {
|
||||
private AccountGroup lookup(final AccountGroup.NameKey groupName)
|
||||
throws OrmException {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
|
||||
|
||||
final AccountGroup group = db.accountGroups().get(nameKey);
|
||||
if (group != null) {
|
||||
return group;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return db.accountGroups().get(groupName);
|
||||
} finally {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
25
src/main/java/com/google/gerrit/server/account/Realm.java
Normal file
25
src/main/java/com/google/gerrit/server/account/Realm.java
Normal 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);
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server.config;
|
||||
|
||||
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.SystemConfig;
|
||||
import com.google.gwtjsonrpc.server.SignedToken;
|
||||
@@ -25,6 +26,9 @@ import com.google.inject.Singleton;
|
||||
import org.spearce.jgit.lib.Config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** Authentication related settings from {@code gerrit.config}. */
|
||||
@Singleton
|
||||
@@ -35,6 +39,10 @@ public class AuthConfig {
|
||||
private final String[] trusted;
|
||||
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;
|
||||
|
||||
@Inject
|
||||
@@ -46,6 +54,13 @@ public class AuthConfig {
|
||||
trusted = toTrusted(cfg);
|
||||
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 =
|
||||
cfg.getBoolean("auth", "allowgoogleaccountupgrade", false);
|
||||
}
|
||||
@@ -104,10 +119,26 @@ public class AuthConfig {
|
||||
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) {
|
||||
switch (getLoginType()) {
|
||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||
case HTTP:
|
||||
case HTTP_LDAP:
|
||||
// Its safe to assume yes for an HTTP authentication type, as the
|
||||
// only way in is through some external system that the admin trusts
|
||||
//
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.inject.Scopes.SINGLETON;
|
||||
import static com.google.inject.Stage.PRODUCTION;
|
||||
|
||||
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.MergeOp;
|
||||
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.AccountCache;
|
||||
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.GroupCache;
|
||||
import com.google.gerrit.server.account.Realm;
|
||||
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.AddReviewerSender;
|
||||
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.workflow.FunctionState;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import org.spearce.jgit.lib.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -70,12 +77,30 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
public static Injector createInjector(final Injector db) {
|
||||
final Injector cfg = db.createChildInjector(new GerritConfigModule());
|
||||
final List<Module> modules = new ArrayList<Module>();
|
||||
modules.add(new GerritGlobalModule());
|
||||
modules.add(cfg.getInstance(GerritGlobalModule.class));
|
||||
return cfg.createChildInjector(modules);
|
||||
}
|
||||
|
||||
private final LoginType loginType;
|
||||
|
||||
@Inject
|
||||
GerritGlobalModule(final AuthConfig authConfig,
|
||||
@GerritServerConfig final Config config) {
|
||||
loginType = authConfig.getLoginType();
|
||||
}
|
||||
|
||||
@Override
|
||||
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(
|
||||
SINGLETON);
|
||||
bind(EmailExpander.class).toProvider(EmailExpanderProvider.class).in(
|
||||
|
||||
@@ -70,6 +70,7 @@ class WebModule extends FactoryModule {
|
||||
break;
|
||||
|
||||
case HTTP:
|
||||
case HTTP_LDAP:
|
||||
install(new HttpAuthModule());
|
||||
break;
|
||||
|
||||
|
||||
39
src/main/java/com/google/gerrit/server/ldap/LdapModule.java
Normal file
39
src/main/java/com/google/gerrit/server/ldap/LdapModule.java
Normal 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);
|
||||
}
|
||||
}
|
||||
121
src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
Normal file
121
src/main/java/com/google/gerrit/server/ldap/LdapQuery.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
285
src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
Normal file
285
src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroupMember;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroupMemberAudit;
|
||||
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.NoSuchAccountException;
|
||||
import com.google.gerrit.client.rpc.NoSuchEntityException;
|
||||
@@ -154,7 +155,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
|
||||
assertAmGroupOwner(db, group);
|
||||
group.setDescription(description);
|
||||
db.accountGroups().update(Collections.singleton(group));
|
||||
groupCache.evict(groupId);
|
||||
groupCache.evict(group);
|
||||
return VoidResult.INSTANCE;
|
||||
}
|
||||
});
|
||||
@@ -175,7 +176,7 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
|
||||
|
||||
group.setOwnerGroupId(owner.getId());
|
||||
db.accountGroups().update(Collections.singleton(group));
|
||||
groupCache.evict(groupId);
|
||||
groupCache.evict(group);
|
||||
return VoidResult.INSTANCE;
|
||||
}
|
||||
});
|
||||
@@ -188,14 +189,16 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
|
||||
final AccountGroup group = db.accountGroups().get(groupId);
|
||||
assertAmGroupOwner(db, group);
|
||||
|
||||
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newName);
|
||||
if (!nameKey.equals(group.getNameKey())) {
|
||||
if (db.accountGroups().get(nameKey) != null) {
|
||||
final AccountGroup.NameKey oldKey = group.getNameKey();
|
||||
final AccountGroup.NameKey newKey = new AccountGroup.NameKey(newName);
|
||||
if (!newKey.equals(oldKey)) {
|
||||
if (db.accountGroups().get(newKey) != null) {
|
||||
throw new Failure(new NameAlreadyUsedException());
|
||||
}
|
||||
group.setNameKey(nameKey);
|
||||
group.setNameKey(newKey);
|
||||
db.accountGroups().update(Collections.singleton(group));
|
||||
groupCache.evict(groupId);
|
||||
groupCache.evict(group);
|
||||
groupCache.evictAfterRename(oldKey);
|
||||
}
|
||||
return VoidResult.INSTANCE;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.google.gerrit.client.reviewdb.Project.SubmitType;
|
||||
import com.google.gerrit.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
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.BaseCommand;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
@@ -66,6 +67,9 @@ final class AdminCreateProject extends BaseCommand {
|
||||
@Inject
|
||||
private GroupCache groupCache;
|
||||
|
||||
@Inject
|
||||
private AuthConfig authConfig;
|
||||
|
||||
@Inject
|
||||
private ReplicationQueue rq;
|
||||
|
||||
@@ -167,7 +171,7 @@ final class AdminCreateProject extends BaseCommand {
|
||||
}
|
||||
|
||||
if (ownerName == null) {
|
||||
ownerId = groupCache.getAdministrators();
|
||||
ownerId = authConfig.getAdministratorsGroup();
|
||||
} else {
|
||||
AccountGroup ownerGroup = groupCache.lookup(ownerName);
|
||||
if (ownerGroup == null) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.google.gerrit.client.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.ProjectRight;
|
||||
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.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
@@ -50,7 +50,7 @@ public class FunctionState {
|
||||
}
|
||||
|
||||
private final ApprovalTypes approvalTypes;
|
||||
private final AccountCache accountCache;
|
||||
private final IdentifiedUser.GenericFactory userFactory;
|
||||
private final ProjectCache projectCache;
|
||||
|
||||
private final Map<ApprovalCategory.Id, Collection<PatchSetApproval>> approvals =
|
||||
@@ -67,12 +67,12 @@ public class FunctionState {
|
||||
|
||||
@Inject
|
||||
FunctionState(final ApprovalTypes approvalTypes, final ProjectCache pc,
|
||||
final AccountCache ac, final GroupCache egc, @Assisted final Change c,
|
||||
@Assisted final PatchSet.Id psId,
|
||||
final IdentifiedUser.GenericFactory userFactory, final GroupCache egc,
|
||||
@Assisted final Change c, @Assisted final PatchSet.Id psId,
|
||||
@Assisted final Collection<PatchSetApproval> all) {
|
||||
this.approvalTypes = approvalTypes;
|
||||
this.userFactory = userFactory;
|
||||
projectCache = pc;
|
||||
accountCache = ac;
|
||||
|
||||
change = c;
|
||||
project = projectCache.get(change.getDest().getParentKey());
|
||||
@@ -231,7 +231,7 @@ public class FunctionState {
|
||||
for (final ProjectRight r : getAllRights(a.getCategoryId())) {
|
||||
final Account.Id who = a.getAccountId();
|
||||
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());
|
||||
maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user