blob: 97a03098bbd0cb92e61065ebda56375f667c7221 [file] [log] [blame]
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.auth.ldap;
import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR;
import static com.google.gerrit.server.auth.ldap.Helper.LDAP_UUID;
import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_CACHE;
import static com.google.gerrit.server.auth.ldap.LdapModule.GROUP_EXIST_CACHE;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.ParameterizedString;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.security.auth.login.LoginException;
/**
* Implementation of GroupBackend for the LDAP group system.
*/
public class LdapGroupBackend implements GroupBackend {
private static final Logger log = LoggerFactory.getLogger(LdapGroupBackend.class);
private static final String LDAP_NAME = "ldap/";
private static final String GROUPNAME = "groupname";
private final Helper helper;
private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
private final LoadingCache<String, Boolean> existsCache;
private final ProjectCache projectCache;
private final Provider<CurrentUser> userProvider;
@Inject
LdapGroupBackend(
Helper helper,
@Named(GROUP_CACHE) LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
@Named(GROUP_EXIST_CACHE) LoadingCache<String, Boolean> existsCache,
ProjectCache projectCache,
Provider<CurrentUser> userProvider) {
this.helper = helper;
this.membershipCache = membershipCache;
this.projectCache = projectCache;
this.existsCache = existsCache;
this.userProvider = userProvider;
}
private boolean isLdapUUID(AccountGroup.UUID uuid) {
return uuid.get().startsWith(LDAP_UUID);
}
private static GroupReference groupReference(ParameterizedString p,
LdapQuery.Result res) throws NamingException {
return new GroupReference(
new AccountGroup.UUID(LDAP_UUID + res.getDN()),
LDAP_NAME + LdapRealm.apply(p, res));
}
private static String cnFor(String dn) {
try {
LdapName name = new LdapName(dn);
if (!name.isEmpty()) {
String cn = name.get(name.size() - 1);
int index = cn.indexOf('=');
if (index >= 0) {
cn = cn.substring(index + 1);
}
return cn;
}
} catch (InvalidNameException e) {
log.warn("Cannot parse LDAP dn for cn", e);
}
return dn;
}
@Override
public boolean handles(AccountGroup.UUID uuid) {
return isLdapUUID(uuid);
}
@Override
public GroupDescription.Basic get(final AccountGroup.UUID uuid) {
if (!handles(uuid)) {
return null;
}
String groupDn = uuid.get().substring(LDAP_UUID.length());
CurrentUser user = userProvider.get();
if (!(user instanceof IdentifiedUser)
|| !membershipsOf((IdentifiedUser) user).contains(uuid)) {
try {
if (!existsCache.get(groupDn)) {
return null;
}
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup group %s in LDAP", groupDn), e);
return null;
}
}
final String name = LDAP_NAME + cnFor(groupDn);
return new GroupDescription.Basic() {
@Override
public AccountGroup.UUID getGroupUUID() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
@Nullable
public String getEmailAddress() {
return null;
}
@Override
@Nullable
public String getUrl() {
return null;
}
};
}
@Override
public Collection<GroupReference> suggest(String name, ProjectControl project) {
AccountGroup.UUID uuid = new AccountGroup.UUID(name);
if (isLdapUUID(uuid)) {
GroupDescription.Basic g = get(uuid);
if (g == null) {
return Collections.emptySet();
}
return Collections.singleton(GroupReference.forGroup(g));
} else if (name.startsWith(LDAP_NAME)) {
return suggestLdap(name.substring(LDAP_NAME.length()));
}
return Collections.emptySet();
}
@Override
public GroupMembership membershipsOf(IdentifiedUser user) {
String id = findId(user.state().getExternalIds());
if (id == null) {
return GroupMembership.EMPTY;
}
try {
final Set<AccountGroup.UUID> groups = membershipCache.get(id);
return new ListGroupMembership(groups) {
@Override
public Set<AccountGroup.UUID> getKnownGroups() {
Set<AccountGroup.UUID> g = Sets.newHashSet(groups);
g.retainAll(projectCache.guessRelevantGroupUUIDs());
return g;
}
};
} catch (ExecutionException e) {
log.warn(String.format("Cannot lookup membershipsOf %s in LDAP", id), e);
return GroupMembership.EMPTY;
}
}
private static String findId(final Collection<AccountExternalId> ids) {
for (final AccountExternalId i : ids) {
if (i.isScheme(AccountExternalId.SCHEME_GERRIT)) {
return i.getSchemeRest();
}
}
return null;
}
private Set<GroupReference> suggestLdap(String name) {
if (name.isEmpty()) {
return Collections.emptySet();
}
Set<GroupReference> out = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR);
try {
DirContext ctx = helper.open();
try {
// Do exact lookups until there are at least 3 characters.
name = Rdn.escapeValue(name) + ((name.length() >= 3) ? "*" : "");
LdapSchema schema = helper.getSchema(ctx);
ParameterizedString filter = ParameterizedString.asis(
schema.groupPattern.replace(GROUPNAME, name).toString());
Set<String> returnAttrs =
new HashSet<String>(schema.groupName.getParameterNames());
Map<String, String> params = Collections.emptyMap();
for (String groupBase : schema.groupBases) {
LdapQuery query = new LdapQuery(
groupBase, schema.groupScope, filter, returnAttrs);
for (LdapQuery.Result res : query.query(ctx, params)) {
out.add(groupReference(schema.groupName, res));
}
}
} finally {
try {
ctx.close();
} catch (NamingException e) {
log.warn("Cannot close LDAP query handle", e);
}
}
} catch (NamingException e) {
log.warn("Cannot query LDAP for groups matching requested name", e);
} catch (LoginException e) {
log.warn("Cannot query LDAP for groups matching requested name", e);
}
return out;
}
}