LCOV - code coverage report
Current view: top level - auth/ldap - LdapGroupBackend.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 14 77 18.2 %
Date: 2022-11-19 15:00:39 Functions: 4 17 23.5 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750