diff options
Diffstat (limited to 'java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java')
-rw-r--r-- | java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java new file mode 100644 index 0000000000..87a4abfacb --- /dev/null +++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java @@ -0,0 +1,233 @@ +// 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.account.externalids.ExternalId.SCHEME_GERRIT; +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.common.flogger.FluentLogger; +import com.google.gerrit.common.Nullable; +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.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.externalids.ExternalId; +import com.google.gerrit.server.auth.ldap.Helper.LdapSchema; +import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +import java.io.IOException; +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.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; +import org.eclipse.jgit.lib.Config; + +/** Implementation of GroupBackend for the LDAP group system. */ +public class LdapGroupBackend implements GroupBackend { + static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + 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; + private final Config gerritConfig; + + @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, + @GerritServerConfig Config gerritConfig) { + this.helper = helper; + this.membershipCache = membershipCache; + this.projectCache = projectCache; + this.existsCache = existsCache; + this.userProvider = userProvider; + this.gerritConfig = gerritConfig; + } + + 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) { + logger.atWarning().withCause(e).log("Cannot parse LDAP dn for cn"); + } + return dn; + } + + @Override + public boolean handles(AccountGroup.UUID uuid) { + return isLdapUUID(uuid); + } + + @Override + public GroupDescription.Basic get(AccountGroup.UUID uuid) { + if (!handles(uuid)) { + return null; + } + + String groupDn = uuid.get().substring(LDAP_UUID.length()); + CurrentUser user = userProvider.get(); + if (!(user.isIdentifiedUser()) || !membershipsOf(user.asIdentifiedUser()).contains(uuid)) { + try { + if (!existsCache.get(groupDn)) { + return null; + } + } catch (ExecutionException e) { + logger.atWarning().withCause(e).log("Cannot lookup group %s in LDAP", groupDn); + 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, ProjectState 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; + } + return new LdapGroupMembership(membershipCache, projectCache, id, gerritConfig); + } + + private static String findId(Collection<ExternalId> extIds) { + for (ExternalId extId : extIds) { + if (extId.isScheme(SCHEME_GERRIT)) { + return extId.key().id(); + } + } + 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<>(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 { + helper.close(ctx); + } + } catch (IOException | NamingException | LoginException e) { + logger.atWarning().withCause(e).log("Cannot query LDAP for groups matching requested name"); + } + return out; + } + + @Override + public boolean isVisibleToAll(AccountGroup.UUID uuid) { + return handles(uuid) && helper.groupsVisibleToAll(); + } +} |