Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.auth.ldap; 16 : 17 : import static com.google.gerrit.auth.ldap.Helper.LDAP_UUID; 18 : import static com.google.gerrit.auth.ldap.LdapModule.GROUP_CACHE; 19 : import static com.google.gerrit.auth.ldap.LdapModule.GROUP_EXIST_CACHE; 20 : import static com.google.gerrit.server.account.GroupBackends.GROUP_REF_NAME_COMPARATOR; 21 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT; 22 : 23 : import com.google.common.cache.LoadingCache; 24 : import com.google.common.collect.Sets; 25 : import com.google.common.flogger.FluentLogger; 26 : import com.google.gerrit.auth.ldap.Helper.LdapSchema; 27 : import com.google.gerrit.common.Nullable; 28 : import com.google.gerrit.common.data.ParameterizedString; 29 : import com.google.gerrit.entities.AccountGroup; 30 : import com.google.gerrit.entities.GroupDescription; 31 : import com.google.gerrit.entities.GroupReference; 32 : import com.google.gerrit.server.CurrentUser; 33 : import com.google.gerrit.server.account.GroupBackend; 34 : import com.google.gerrit.server.account.GroupMembership; 35 : import com.google.gerrit.server.account.externalids.ExternalId; 36 : import com.google.gerrit.server.config.GerritServerConfig; 37 : import com.google.gerrit.server.project.ProjectCache; 38 : import com.google.gerrit.server.project.ProjectState; 39 : import com.google.inject.Inject; 40 : import com.google.inject.Provider; 41 : import com.google.inject.name.Named; 42 : import java.io.IOException; 43 : import java.util.Collection; 44 : import java.util.Collections; 45 : import java.util.HashSet; 46 : import java.util.Map; 47 : import java.util.Optional; 48 : import java.util.Set; 49 : import java.util.concurrent.ExecutionException; 50 : import javax.naming.InvalidNameException; 51 : import javax.naming.NamingException; 52 : import javax.naming.directory.DirContext; 53 : import javax.naming.ldap.LdapName; 54 : import javax.naming.ldap.Rdn; 55 : import javax.security.auth.login.LoginException; 56 : import org.eclipse.jgit.lib.Config; 57 : 58 : /** Implementation of GroupBackend for the LDAP group system. */ 59 : public class LdapGroupBackend implements GroupBackend { 60 2 : static final FluentLogger logger = FluentLogger.forEnclosingClass(); 61 : 62 : private static final String LDAP_NAME = "ldap/"; 63 : private static final String GROUPNAME = "groupname"; 64 : 65 : private final Helper helper; 66 : private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache; 67 : private final LoadingCache<String, Boolean> existsCache; 68 : private final ProjectCache projectCache; 69 : private final Provider<CurrentUser> userProvider; 70 : private final Config gerritConfig; 71 : 72 : @Inject 73 : LdapGroupBackend( 74 : Helper helper, 75 : @Named(GROUP_CACHE) LoadingCache<String, Set<AccountGroup.UUID>> membershipCache, 76 : @Named(GROUP_EXIST_CACHE) LoadingCache<String, Boolean> existsCache, 77 : ProjectCache projectCache, 78 : Provider<CurrentUser> userProvider, 79 2 : @GerritServerConfig Config gerritConfig) { 80 2 : this.helper = helper; 81 2 : this.membershipCache = membershipCache; 82 2 : this.projectCache = projectCache; 83 2 : this.existsCache = existsCache; 84 2 : this.userProvider = userProvider; 85 2 : this.gerritConfig = gerritConfig; 86 2 : } 87 : 88 : private boolean isLdapUUID(AccountGroup.UUID uuid) { 89 0 : return uuid.get().startsWith(LDAP_UUID); 90 : } 91 : 92 : private static GroupReference groupReference(ParameterizedString p, LdapQuery.Result res) 93 : throws NamingException { 94 0 : return GroupReference.create( 95 0 : AccountGroup.uuid(LDAP_UUID + res.getDN()), LDAP_NAME + LdapRealm.apply(p, res)); 96 : } 97 : 98 : private static String cnFor(String dn) { 99 : try { 100 0 : LdapName name = new LdapName(dn); 101 0 : if (!name.isEmpty()) { 102 0 : String cn = name.get(name.size() - 1); 103 0 : int index = cn.indexOf('='); 104 0 : if (index >= 0) { 105 0 : cn = cn.substring(index + 1); 106 : } 107 0 : return cn; 108 : } 109 0 : } catch (InvalidNameException e) { 110 0 : logger.atWarning().withCause(e).log("Cannot parse LDAP dn for cn"); 111 0 : } 112 0 : return dn; 113 : } 114 : 115 : @Override 116 : public boolean handles(AccountGroup.UUID uuid) { 117 0 : return isLdapUUID(uuid); 118 : } 119 : 120 : @Nullable 121 : @Override 122 : public GroupDescription.Basic get(AccountGroup.UUID uuid) { 123 0 : if (!handles(uuid)) { 124 0 : return null; 125 : } 126 : 127 0 : String groupDn = uuid.get().substring(LDAP_UUID.length()); 128 0 : CurrentUser user = userProvider.get(); 129 0 : if (!user.isIdentifiedUser() || !membershipsOf(user.asIdentifiedUser()).contains(uuid)) { 130 : try { 131 0 : if (!existsCache.get(groupDn)) { 132 0 : return null; 133 : } 134 0 : } catch (ExecutionException e) { 135 0 : logger.atWarning().withCause(e).log("Cannot lookup group %s in LDAP", groupDn); 136 0 : return null; 137 0 : } 138 : } 139 : 140 0 : final String name = LDAP_NAME + cnFor(groupDn); 141 0 : return new GroupDescription.Basic() { 142 : @Override 143 : public AccountGroup.UUID getGroupUUID() { 144 0 : return uuid; 145 : } 146 : 147 : @Override 148 : public String getName() { 149 0 : return name; 150 : } 151 : 152 : @Override 153 : @Nullable 154 : public String getEmailAddress() { 155 0 : return null; 156 : } 157 : 158 : @Override 159 : @Nullable 160 : public String getUrl() { 161 0 : return null; 162 : } 163 : }; 164 : } 165 : 166 : @Override 167 : public Collection<GroupReference> suggest(String name, ProjectState project) { 168 0 : AccountGroup.UUID uuid = AccountGroup.uuid(name); 169 0 : if (isLdapUUID(uuid)) { 170 0 : GroupDescription.Basic g = get(uuid); 171 0 : if (g == null) { 172 0 : return Collections.emptySet(); 173 : } 174 0 : return Collections.singleton(GroupReference.forGroup(g)); 175 0 : } else if (name.startsWith(LDAP_NAME)) { 176 0 : return suggestLdap(name.substring(LDAP_NAME.length())); 177 : } 178 0 : return Collections.emptySet(); 179 : } 180 : 181 : @Override 182 : public GroupMembership membershipsOf(CurrentUser user) { 183 1 : Optional<ExternalId.Key> id = 184 1 : user.getExternalIdKeys().stream().filter(e -> e.isScheme(SCHEME_GERRIT)).findAny(); 185 1 : if (!id.isPresent()) { 186 1 : return GroupMembership.EMPTY; 187 : } 188 1 : return new LdapGroupMembership(membershipCache, projectCache, id.get().id(), gerritConfig); 189 : } 190 : 191 : private Set<GroupReference> suggestLdap(String name) { 192 0 : if (name.isEmpty()) { 193 0 : return Collections.emptySet(); 194 : } 195 : 196 0 : Set<GroupReference> out = Sets.newTreeSet(GROUP_REF_NAME_COMPARATOR); 197 : try { 198 0 : DirContext ctx = helper.open(); 199 : try { 200 : // Do exact lookups until there are at least 3 characters. 201 0 : name = Rdn.escapeValue(name) + ((name.length() >= 3) ? "*" : ""); 202 0 : LdapSchema schema = helper.getSchema(ctx); 203 0 : ParameterizedString filter = 204 0 : ParameterizedString.asis(schema.groupPattern.replace(GROUPNAME, name).toString()); 205 0 : Set<String> returnAttrs = new HashSet<>(schema.groupName.getParameterNames()); 206 0 : Map<String, String> params = Collections.emptyMap(); 207 0 : for (String groupBase : schema.groupBases) { 208 0 : LdapQuery query = new LdapQuery(groupBase, schema.groupScope, filter, returnAttrs); 209 : for (LdapQuery.Result res : 210 0 : query.query(ctx, params, helper.getGroupSearchLatencyTimer())) { 211 0 : out.add(groupReference(schema.groupName, res)); 212 0 : } 213 0 : } 214 : } finally { 215 0 : helper.close(ctx); 216 : } 217 0 : } catch (IOException | NamingException | LoginException e) { 218 0 : logger.atWarning().withCause(e).log("Cannot query LDAP for groups matching requested name"); 219 0 : } 220 0 : return out; 221 : } 222 : 223 : @Override 224 : public boolean isVisibleToAll(AccountGroup.UUID uuid) { 225 0 : return handles(uuid) && helper.groupsVisibleToAll(); 226 : } 227 : }