Package com.google.enterprise.connector.sharepoint.ldap

Source Code of com.google.enterprise.connector.sharepoint.ldap.UserGroupsService$LdapConnectionSettings

// Copyright 2011 Google Inc.
//
// 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.enterprise.connector.sharepoint.ldap;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.enterprise.connector.sharepoint.client.SPConstants;
import com.google.enterprise.connector.sharepoint.client.SharepointClientContext;
import com.google.enterprise.connector.sharepoint.client.Util;
import com.google.enterprise.connector.sharepoint.dao.UserDataStoreDAO;
import com.google.enterprise.connector.sharepoint.dao.UserGroupMembership;
import com.google.enterprise.connector.sharepoint.ldap.LdapConstants.AuthType;
import com.google.enterprise.connector.sharepoint.ldap.LdapConstants.LdapConnectionError;
import com.google.enterprise.connector.sharepoint.ldap.LdapConstants.Method;
import com.google.enterprise.connector.sharepoint.ldap.LdapConstants.ServerType;
import com.google.enterprise.connector.sharepoint.ldap.UserGroupsService.LdapConnection;
import com.google.enterprise.connector.sharepoint.ldap.UserGroupsService.LdapConnectionSettings;
import com.google.enterprise.connector.sharepoint.spiimpl.SharepointAuthenticationManager;
import com.google.enterprise.connector.sharepoint.spiimpl.SharepointException;
import com.google.enterprise.connector.spi.Principal;
import com.google.enterprise.connector.spi.SpiConstants.PrincipalType;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.AuthenticationException;
import javax.naming.AuthenticationNotSupportedException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

/**
* An implementation of {@link LdapService} and encapsulates all interaction
* with JNDI to get {@link LdapContext} and {@link LdapConnection} with
* {@link LdapConnectionSettings} provided by
* {@link SharepointAuthenticationManager} and also it talks to
* {@link UserDataStoreDAO} to get all SP groups. This implementation is
* specific to Active Directory service at the moment.
*/
public class UserGroupsService implements LdapService {

  private static final Logger LOGGER = Logger.getLogger(UserGroupsService.class.getName());

  private LdapConnectionSettings ldapConnectionSettings;
  private final UserGroupsCache<Object,
      ConcurrentHashMap<String, Set<Principal>>> lugCacheStore;
  private LdapConnection ldapConnection;
  private final SharepointClientContext sharepointClientContext;

  /**
   * Initializes LDAP context object for a given {@link LdapConnectionSettings}
   * and also Constructs {@code LdapUserGroupsCache} cache with a refresh
   * interval and custom capacity.
   *
   * @param ldapConnectionSettings
   */
  @VisibleForTesting
  public UserGroupsService(LdapConnectionSettings ldapConnectionSettings,
      int cacheSize, long refreshInterval, boolean enableLUGCache) {
    this.ldapConnectionSettings = ldapConnectionSettings;
    ldapConnection = new LdapConnection(ldapConnectionSettings);
    this.sharepointClientContext = null;
    if (enableLUGCache) {
      this.lugCacheStore = new UserGroupsCache<Object,
          ConcurrentHashMap<String, Set<Principal>>>(
              refreshInterval, cacheSize);
      LOGGER.log(Level.CONFIG, "Configured user groups cache store with refresh interval [ "
          + refreshInterval + " ] and with capacity [ " + cacheSize + " ]");
    } else {
      this.lugCacheStore = null;
      LOGGER.log(Level.CONFIG, "No cache has been configured to keep user groups memberships.");
    }
  }

  private UserGroupsService(LdapConnectionSettings ldapConnectionSettings,
      SharepointClientContext inSharepointClientContext) {
    if (!Strings.isNullOrEmpty(ldapConnectionSettings.getHostname())) {
      this.ldapConnectionSettings = ldapConnectionSettings;
      ldapConnection = new LdapConnection(ldapConnectionSettings);
    } else {
      LOGGER.warning("Not attempting to create LDAP context, because LDAP host name is empty or null.");
    }

    this.sharepointClientContext = inSharepointClientContext;
    if (sharepointClientContext.isUseCacheToStoreLdapUserGroupsMembership()) {
      this.lugCacheStore = new UserGroupsCache<Object,
          ConcurrentHashMap<String, Set<Principal>>>(
              sharepointClientContext.getCacheRefreshInterval(),
              sharepointClientContext.getInitialCacheSize());
    } else {
      this.lugCacheStore = null;
      LOGGER.log(Level.INFO, "No cache has been configured to keep user groups memberships.");
    }
  }

  public UserGroupsService(SharepointClientContext inSharepointClientContext) {
    this(inSharepointClientContext.getLdapConnectionSettings(),
        inSharepointClientContext);
  }

  /**
   * A setter method used to set {@link LdapConnectionSettings} and creates a
   * {@link LdapConnection} object.
   *
   * @param ldapConnectionSettings to initialize and create
   *          {@link LdapConnection}
   */
  public void setLdapConnectionSettings(
      LdapConnectionSettings ldapConnectionSettings) {
    this.ldapConnectionSettings = ldapConnectionSettings;
    ldapConnection = new LdapConnection(ldapConnectionSettings);
  }

  public UserGroupsCache<Object, ConcurrentHashMap<String, Set<Principal>>> getLugCacheStore() {
    return lugCacheStore;
  }

  public LdapConnection getLdapConnection() {
    return ldapConnection;
  }

  @Override
  public LdapContext getLdapContext() {
    return ldapConnection.getLdapContext();
  }

  public Map<LdapConnectionError, String> getErrors() {
    if (ldapConnection != null) {
      return ldapConnection.getErrors();
    }
    throw new IllegalStateException(
        "Must successfully set connection config before getting error state");
  }

  public static class LdapConnection {

    private final LdapConnectionSettings settings;
    private LdapContext ldapContext = null;
    private final Map<LdapConnectionError, String> errors;

    public LdapConnection(LdapConnectionSettings ldapConnectionSettings) {
      LOGGER.info(ldapConnectionSettings.toString());
      this.settings = ldapConnectionSettings;
      Hashtable<String, String> env = configureLdapEnvironment();
      this.errors = Maps.newHashMap();
      this.ldapContext = createContext(env);
    }

    /**
     * @return Map of errors with {@link LdapConnectionError} as a key and
     *         detailed error message as a value.
     */
    public Map<LdapConnectionError, String> getErrors() {
      return errors;
    }

    /**
     * Returns initial {@link LdapContext}
     */
    public LdapContext getLdapContext() {
      return ldapContext;
    }

    private LdapContext createContext() {
      return createContext(configureLdapEnvironment());
    }

    /**
     * Returns {@link LdapContext} object.
     *
     * @param env hold LDAP
     * @return {@link LdapContext}
     */
    private LdapContext createContext(Hashtable<String, String> env) {
      LdapContext ctx = null;
      try {
        ctx = new InitialLdapContext(env, null);
      } catch (CommunicationException e) {
        errors.put(LdapConnectionError.CommunicationException,
            e.getCause() != null ? e.getCause().toString() : null);
        LOGGER.log(Level.WARNING, "Could not obtain an initial context to query LDAP (Active Directory) due to a communication failure.", e);
      } catch (AuthenticationNotSupportedException e) {
        errors.put(LdapConnectionError.AuthenticationNotSupportedException,
            e.getCause() != null ? e.getCause().toString() : null);
        LOGGER.log(Level.WARNING, "Could not obtain an initial context to query LDAP (Active Directory) due to authentication not supported exception.", e);
      } catch (AuthenticationException ae) {
        errors.put(LdapConnectionError.AuthenticationFailedException,
            ae.getCause() != null ? ae.getCause().toString() : null);
        LOGGER.log(Level.WARNING, "Could not obtain an initial context to query LDAP (Active Directory) due to authentication exception.", ae);
      } catch (NamingException e) {
        errors.put(LdapConnectionError.NamingException,
            e.getCause() != null ? e.getCause().toString() : null);
        LOGGER.log(Level.WARNING, "Could not obtain an initial context to query LDAP (Active Directory) due to a naming exception.", e);
      }
      if (ctx == null) {
        return null;
      }
      LOGGER.info("Sucessfully created an Initial LDAP context");
      return ctx;
    }

    /**
     * Makes an LDAP or LDAPS URL. The default port for LDAPS URLs is 636 where
     * as for LDAP URLs it is 389.
     *
     * @return a LDAP or LDAPS URL bases on the {@link Method}
     */
    private String makeLdapUrl() {
      String url;
      Method connectMethod = settings.getConnectMethod();
      if (connectMethod == Method.SSL) {
        url = "ldaps://"; // For SSL
      } else {
        url = "ldap://"; // for NON-SSL
      }

      // Construct the full URL
      url = url + settings.getHostname();
      if (settings.getPort() > 0) {
        url = url + ":" + settings.getPort();
      }

      LOGGER.info("Complete LDAP URL : " + url);
      return url;
    }

    /**
     * Initialize the {@link java.util.HashSet} used to create an initial LDAP Context.
     * Note that we specifically require a {@link java.util.HashSet} rather than a
     * HashMap as the parameter type in the InitialLDAPContext constructor
     *
     * @return initialized {@link java.util.HashSet} suitable for constructing an
     *         InitiaLdaplContext
     */
    private Hashtable<String, String> configureLdapEnvironment() {
      Hashtable<String, String> env = new Hashtable<String, String>();
      // Use the built-in LDAP support.
      env.put(Context.INITIAL_CONTEXT_FACTORY, LdapConstants.COM_SUN_JNDI_LDAP_LDAP_CTX_FACTORY);

      // Set our authentication settings.
      AuthType authType = settings.getAuthType();
      if (authType == AuthType.SIMPLE) {
        env.put(Context.SECURITY_AUTHENTICATION, authType.toString().toLowerCase());
        env.put(Context.SECURITY_PRINCIPAL, settings.getUsername()
            + SPConstants.AT + settings.domainName);
        env.put(Context.SECURITY_CREDENTIALS, settings.getPassword());
        LOGGER.info("Using simple authentication.");
      } else {
        if (authType != AuthType.ANONYMOUS) {
          LOGGER.warning("Unknown authType - falling back to anonymous.");
        } else {
          LOGGER.info("Using anonymous authentication.");
        }
        env.put(Context.SECURITY_AUTHENTICATION, "none"); //$NON-NLS-1$
      }
      env.put(Context.PROVIDER_URL, makeLdapUrl());
      return env;
    }
  }

  public static class LdapConnectionSettings {
    private final String hostName;
    private final int port;
    private String domainName;
    private final AuthType authType;
    private final String userName;
    private final String password;
    private final Method connectMethod;
    private final String baseDN;
    private final ServerType serverType;

    public LdapConnectionSettings(Method connectMethod, String hostname,
        int port, String baseDN, AuthType authType, String userName,
        String password, String domainName) {
      this.authType = authType;
      this.baseDN = baseDN;
      this.connectMethod = connectMethod;
      this.hostName = hostname;
      this.password = password;
      this.port = port;
      this.serverType = ServerType.getDefault();
      this.userName = userName;
      this.domainName = domainName;
    }

    public LdapConnectionSettings(Method standard, String hostName, int port,
        String baseDN, String domainName) {
      this.authType = AuthType.ANONYMOUS;
      this.baseDN = baseDN;
      this.connectMethod = standard;
      this.hostName = hostName;
      this.password = null;
      this.port = port;
      this.serverType = ServerType.getDefault();
      this.userName = null;
      this.domainName = domainName;
    }

    @Override
    public String toString() {
      String displayPassword;
      if (password == null) {
        displayPassword = "null";
      } else if (password.length() < 1) {
        displayPassword = "<empty>";
      } else {
        displayPassword = "####";
      }
      return "LdapConnectionSettings [authType=" + authType + ", baseDN="
          + baseDN + ", connectMethod=" + connectMethod + ", hostname="
          + hostName + ", password=" + displayPassword + ", port=" + port
          + ", serverType=" + serverType + ", userName=" + userName
          + ", domainName =" + domainName + " ]";
    }

    public AuthType getAuthType() {
      return authType;
    }

    public String getBaseDN() {
      return baseDN;
    }

    public Method getConnectMethod() {
      return connectMethod;
    }

    public String getHostname() {
      return hostName;
    }

    public String getPassword() {
      return password;
    }

    public int getPort() {
      return port;
    }

    public ServerType getServerType() {
      return serverType;
    }

    public String getUsername() {
      return userName;
    }

    public String getDomainName() {
      return domainName;
    }
  }

  /**
   * Takes user SID as binary string, group RID as string and converts them to escaped hexa
   * representation of LDAP search filter
   *
   * @param sid user binary SID
   * @param primaryGroupId primary group RID (guaranteed to be within user's domain)
   * @return string containing LDAP search filter for user's primary group
   */
  String createSearchFilterForPrimaryGroup(byte[] sid, String primaryGroupId) {
    long primaryGroup = Long.parseLong(primaryGroupId);
    // replace the last four bytes of user's SID with group RID
    sid[sid.length - 1] = (byte)((primaryGroup >> 24) & 0xFF);
    sid[sid.length - 2] = (byte)((primaryGroup >> 16) & 0xFF);
    sid[sid.length - 3] = (byte)((primaryGroup >> 8) & 0xFF);
    sid[sid.length - 4] = (byte)(primaryGroup & 0xFF);
    // format the SID as escaped hexa (i.e. \01\05\ff...)
    StringBuilder primaryGroupSid = new StringBuilder();
    primaryGroupSid.append(LdapConstants.PREFIX_FOR_PRIMARY_GROUP_FILTER);
    for (int i = 0; i < sid.length; ++i) {
      int unsignedByte = sid[i] & 0xFF;
      // add zero padding for single digits
      if (unsignedByte < 16)
        primaryGroupSid.append("\\0");
      else
        primaryGroupSid.append("\\");
      primaryGroupSid.append(Integer.toHexString(unsignedByte));
    }
    primaryGroupSid.append(")");
    return primaryGroupSid.toString();
  }

  /**
   * Returns user's primary group
   *
   * @param userSid SID of the user in Active Directory
   * @param primaryGroupId domain local ID of the primary group
   * @param context
   * @return string containing the primary group's name
   */
  private String getPrimaryGroupForTheSearchUser(byte[] userSid,
      String primaryGroupId, LdapContext context) {
    if (userSid == null || primaryGroupId == null) {
      return null;
    }
    String primaryGroupDN = null;
    SearchControls searchCtls = makeSearchCtls(new String[]{LdapConstants.ATTRIBUTE_MEMBER_OF});
    searchCtls.setReturningAttributes(new String[]{});
    // Create the search filter
    String searchFilter = createSearchFilterForPrimaryGroup(userSid, primaryGroupId);
    // Specify the Base DN for the search
    String searchBase = ldapConnectionSettings.getBaseDN();
    NamingEnumeration<SearchResult> ldapResults = null;
    try {
      ldapResults = context.search(searchBase, searchFilter, searchCtls);
      SearchResult sr = ldapResults.next();
      primaryGroupDN = sr.getNameInNamespace();
    } catch (NamingException ne) {
      LOGGER.log(Level.WARNING, "Failed to retrieve primary group with SID: ["
          + searchFilter + "]", ne);
    } finally {
      try {
        if (null != ldapResults) {
          ldapResults.close();
        }
      } catch (NamingException e) {
        LOGGER.log(Level.WARNING, "Exception during clean up of ldap results.", e);
      }
    }
    return primaryGroupDN;
  }

  /**
   * Returns a set of all direct groups that the search user belongs to.
   *
   * @param userName search user name
   * @return a set of direct groups that the user belongs to in AD.
   */
  private Set<String> getDirectGroupsForTheSearchUser(String userName,
      LdapContext context) {
    // Create the search controls.
    SearchControls searchCtls = makeSearchCtls(new String[]{
          LdapConstants.ATTRIBUTE_MEMBER_OF,
          LdapConstants.ATTRIBUTE_PRIMARY_GROUP_ID,
          LdapConstants.ATTRIBUTE_OBJECTSID
        });
    // Create the search filter.
    String searchFilter = createSearchFilterForDirectGroups(userName);
    // Specify the Base DN for the search.
    String searchBase = ldapConnectionSettings.getBaseDN();
    int totalResults = 0;
    Set<String> directGroups = new HashSet<String>();
    NamingEnumeration<SearchResult> ldapResults = null;
    byte[] userSid = null;
    String primaryGroupId = null;
    try {
      ldapResults = context.search(searchBase, searchFilter, searchCtls);
      // Loop through the search results
      while (ldapResults.hasMoreElements()) {
        SearchResult sr = ldapResults.next();
        Attributes attrs = sr.getAttributes();
        if (attrs != null) {
          try {
            for (NamingEnumeration<? extends Attribute> ae = attrs.getAll(); ae.hasMore();) {
              Attribute attr = ae.next();
              if (attr.getID().equals(LdapConstants.ATTRIBUTE_OBJECTSID)){
                userSid = (byte[])attr.get(0);
              } else if (attr.getID().equals(LdapConstants.ATTRIBUTE_PRIMARY_GROUP_ID)) {
                primaryGroupId = (String)attr.get(0);
              } else {
                for (NamingEnumeration<?> e = attr.getAll(); e.hasMore(); totalResults++) {
                  directGroups.add(e.next().toString());
                }
              }
            }
          } catch (NamingException e) {
            LOGGER.log(Level.WARNING, "Exception while retrieving direct groups for the search user ["
                + userName + "]", e);
          }
        }
      }
    } catch (NamingException ne) {
      LOGGER.log(Level.WARNING, "Failed to retrieve direct groups for the user name : ["
          + userName + "]", ne);
    } finally {
      if (null != ldapResults) {
        try {
          ldapResults.close();
        } catch (NamingException e) {
          LOGGER.log(Level.WARNING, "Exception during clean up of ldap results for the search user : "
              + userName, e);
        }
      }
    }

    directGroups.add(
        getPrimaryGroupForTheSearchUser(userSid, primaryGroupId, context));
    LOGGER.info("[ " + userName + " ] is a direct member of "
        + directGroups.size() + " groups : " + directGroups);
    return directGroups;
  }

  private SearchControls makeSearchCtls(String attributes[]) {
    SearchControls searchCtls = new SearchControls();
    // Specify the search scope
    searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
    // Specify the attributes to return
    searchCtls.setReturningAttributes(attributes);
    return searchCtls;
  }

  private String createSearchFilterForDirectGroups(String userName) {
    StringBuffer filter;
    filter = new StringBuffer().append(LdapConstants.PREFIX_FOR_DIRECT_GROUPS_FILTER
        + ldapEscape(userName) + SPConstants.DOUBLE_CLOSE_PARENTHESIS);
    LOGGER.config("search filter value for fetching direct groups :" + filter);
    return filter.toString();
  }

  @VisibleForTesting
  void getAllParentGroups(String groupName, Set<String> parentGroupsInfo,
      LdapContext context) {
    if (!Strings.isNullOrEmpty(groupName)) {
      parentGroupsInfo.add(groupName);
      Set<String> parentGroups =
          getAllParentGroupsForTheGroup(groupName, context);
      LOGGER.log(Level.INFO, "Parent groups for the group [" + groupName
          + "] : " + parentGroups);

      for (String group : parentGroups) {
        if (!parentGroupsInfo.contains(group)) {
          getAllParentGroups(group, parentGroupsInfo, context);
        }
      }
    }
  }

  /**
   * Returns a set of all parent groups that the search user belongs to.
   *
   * @param groupName is the group, whose parent groups need to be retrieved.
   * @return a set of all parent groups
   */
  private Set<String> getAllParentGroupsForTheGroup(String groupName,
      LdapContext context) {
    Set<String> parentGroups = new HashSet<String>();
    // Create the search controls
    SearchControls searchCtls = makeSearchCtls(new String[]{LdapConstants.ATTRIBUTE_MEMBER_OF});
    // Create the search filter
    String searchFilter = createSearchFilterForParentGroups(groupName);
    // Specify the Base DN for the search
    String searchBase = ldapConnectionSettings.getBaseDN();
    NamingEnumeration<SearchResult> ldapResults = null;
    try {
      ldapResults = context.search(searchBase, searchFilter, searchCtls);
      while (ldapResults.hasMoreElements()) {
        SearchResult sr = ldapResults.next();
        Attributes attrs = sr.getAttributes();
        if (attrs != null) {
          try {
            for (NamingEnumeration<? extends Attribute> ae = attrs.getAll(); ae.hasMore();) {
              Attribute attr = (Attribute) ae.next();
              for (NamingEnumeration<?> e = attr.getAll(); e.hasMore();) {
                parentGroups.add(e.next().toString());
              }
            }
          } catch (NamingException e) {
            LOGGER.log(Level.WARNING, "Exception while retrieving parent groups for the group ["
                + groupName + "]", e);
          }
        }
      }
    } catch (NamingException ne) {
      LOGGER.log(Level.WARNING, "Failed to retrieve parent groups for the group name : ["
          + groupName + "]", ne);
    } finally {
      try {
        if (null != ldapResults) {
          ldapResults.close();
        }
      } catch (NamingException e) {
        LOGGER.log(Level.WARNING, "Exception during clean up of ldap results.", e);
      }
    }
    return parentGroups;
  }

  /**
   * Escapes special characters used in string literals for LDAP search filters
   *
   * @param literal to be escaped and used in LDAP filter
   * @return escaped literal
   */
  String ldapEscape(String literal) {
    StringBuilder buffer = new StringBuilder(literal.length() * 2);
    for (int i = 0; i < literal.length(); ++i) {
      char c = literal.charAt(i);
      if (LdapConstants.ESCAPE_CHARACTERS.indexOf(c) == -1) {
        buffer.append(c);
      } else {
        String escape = (c < 16) ? "\\0" : "\\";
        buffer.append(escape).append(Integer.toHexString(c));
      }
    }
    return buffer.toString();
  }

  private String createSearchFilterForParentGroups(String groupName) {
    StringBuffer filter;
    filter = new StringBuffer().append(
        LdapConstants.PREFIX_FOR_PARENTS_GROUPS_FILTER
        + ldapEscape(groupName) + SPConstants.DOUBLE_CLOSE_PARENTHESIS);
    return filter.toString();
  }

  @VisibleForTesting
  Set<String> getAllLdapGroups(String userName) {
    if (Strings.isNullOrEmpty(userName)) {
      return null;
    }
    Set<String> ldapGroups = new HashSet<String>();
    LOGGER.info("Querying LDAP directory server to fetch all direct groups "
        + "for the search user: " + userName);
    // fix me by creating a LDAP connection poll instead of creating context
    // object on demand.
    LdapContext context = new LdapConnection(
        sharepointClientContext.getLdapConnectionSettings()).createContext();
    Set<String> directGroups =
        getDirectGroupsForTheSearchUser(userName, context);
    for (String groupName : directGroups) {
      getAllParentGroups(groupName, ldapGroups, context);
    }
    LOGGER.info("[ " + userName + " ] is a direct or indirect member of "
        + ldapGroups.size() + " groups");
    Set<String> groupNames = getSAMAccountNames(ldapGroups, context);
    if (null != directGroups) {
      directGroups = null;
    }
    return groupNames;
  }

  /**
   * Retrieves sAMAccountNames for list of entities
   * @param distinguishedNames set of distinguishedNames of all groups
   *     to resolve
   * @return sAMAccountName for each of the entities
   */
  @VisibleForTesting
  Set<String> getSAMAccountNames(Set<String> distinguishedNames,
      LdapContext context) {
    Set<String> result = new HashSet<String>();
    // Create the search controls
    SearchControls searchCtls = makeSearchCtls(
        new String[] {LdapConstants.ATTRIBUTE_SAMACCOUNTNAME});
    // Create the search filter
    StringBuffer filter = new StringBuffer("(|");
    // AD LDAP allows filters up to 10^7 bytes, we will hit issues, we should
    // hit out of memory in parent group resolution before hitting this limit
    for (String dn : distinguishedNames) {
      filter.append(LdapConstants.PREFIX_FOR_GROUP_FILTER)
          .append(ldapEscape(dn)).append(")");
    }
    filter.append(")");
    // Specify the Base DN for the search
    String searchBase = ldapConnectionSettings.getBaseDN();
    NamingEnumeration<SearchResult> ldapResults = null;
    try {
      ldapResults = context.search(
          searchBase, filter.toString(), searchCtls);
      while (ldapResults.hasMoreElements()) {
        SearchResult sr = ldapResults.next();
        Attributes attrs = sr.getAttributes();
        if (attrs != null) {
          try {
            Attribute sAMAccountName = attrs.get(
                LdapConstants.ATTRIBUTE_SAMACCOUNTNAME);
            if (sAMAccountName == null || sAMAccountName.size() == 0) {
              LOGGER.log(Level.WARNING,
                  "Could not establish sAMAccountName for ["
                  + sr.getNameInNamespace() + "]");
              continue;
            }
            result.add(sAMAccountName.get(0).toString());
          } catch (NamingException e) {
            LOGGER.log(Level.WARNING,
                "Exception while retrieving group names. Search filter ["
                + filter + "]", e);
          }
        }
      }
    } catch (NamingException ne) {
      LOGGER.log(Level.WARNING,
          "Exception while retrieving group names. Search filter ["
          + filter + "]", ne);
    } finally {
      try {
        if (null != ldapResults) {
          ldapResults.close();
        }
      } catch (NamingException e) {
        LOGGER.log(
            Level.WARNING, "Exception during clean up of ldap results.", e);
      }
    }
    return result;
  }

  /**
   * It is a helper method that returns a set of SPGroups for the search user
   * and the AD groups of which he/she is a direct or indirect member of.
   *
   * @param searchUser the searchUser
   * @param adGroups a set of AD groups to which search user is a direct of
   *          indirect member of.
   */
  private Set<Principal> getAllSPGroupsForSearchUserAndLdapGroups(
      String searchUser, Set<Principal> adGroups) {
    String localNamespace = sharepointClientContext.getGoogleLocalNamespace();

    // Search user and SP groups memberships found in user data store.
    Set<Principal> spGroups = new HashSet<Principal>();
    try {
      if (null != this.sharepointClientContext.getUserDataStoreDAO()) {
        spGroups = sharepointClientContext.getUserDataStoreDAO()
            .getSharePointGroupsForSearchUserAndLdapGroups(
                localNamespace, adGroups, searchUser);
      }
    } catch (SharepointException se) {
      LOGGER.warning("Exception occured while fetching user groups memberships for the search user ["
          + searchUser + "] and AD groups [" + adGroups + "]");
    }
    return spGroups;
  }

  @Override
  public Set<Principal> getAllGroupsForSearchUser(
      SharepointClientContext sharepointClientContext, String searchUser)
      throws SharepointException {
    ConcurrentHashMap<String, Set<Principal>> userGroupsMap;
    Set<Principal> allUserGroups = new HashSet<Principal>();
    if (null != searchUser && null != lugCacheStore) {
      if (lugCacheStore.getSize() > 0
          && lugCacheStore.contains(searchUser.toLowerCase())) {
        userGroupsMap = lugCacheStore.get(searchUser.toLowerCase());
        if (null != userGroupsMap) {
          allUserGroups.addAll(userGroupsMap.get(SPConstants.ADGROUPS));
          allUserGroups.addAll(userGroupsMap.get(SPConstants.SPGROUPS));
        }
        LOGGER.info("Found valid entry for search user [" + searchUser
            + "] in cache store and he/she is a direct or indirect member of "
            + allUserGroups.size() + " groups");
        return allUserGroups;
      } else {
        LOGGER.info("No entry found for the user [ "
            + searchUser
            + " ] in cache store. Hence querying LDAP server and User data store to fetch all AD and SP groups, to which the search user belongs to.");
        userGroupsMap = getAllADGroupsAndSPGroupsForSearchUser(searchUser);
        if (null != userGroupsMap) {
          allUserGroups.addAll(userGroupsMap.get(SPConstants.ADGROUPS));
          allUserGroups.addAll(userGroupsMap.get(SPConstants.SPGROUPS));
        }

        this.lugCacheStore.put(searchUser.toLowerCase(), userGroupsMap);

        return allUserGroups;
      }
    } else {
      if (Strings.isNullOrEmpty(searchUser)) {
        return null;
      }
      LOGGER.info("The LDAP cache is not yet initialized and hence querying LDAP and User Data Store directly.");
      userGroupsMap = getAllADGroupsAndSPGroupsForSearchUser(searchUser);
      allUserGroups.addAll(userGroupsMap.get(SPConstants.ADGROUPS));
      allUserGroups.addAll(userGroupsMap.get(SPConstants.SPGROUPS));
    }
    return allUserGroups;
  }

  /**
   * Returns a set of groups after adding the specific group name format
   * provided by connector administrator in the connector configuration page. It
   * should be called before making a call to user data store to get all SP
   * groups.
   *
   * @param groupNames set of AD group names.
   */
  Set<String> addGroupNameFormatForTheGroups(Set<String> groupNames) {
    String format = this.sharepointClientContext.getGroupnameFormatInAce();
    LOGGER.config("Groupname format in ACE : " + format);
    String domain = this.sharepointClientContext.getDomain();
    LOGGER.config("Domain : " + domain);
    Set<String> groups = new HashSet<String>();
    if (format.indexOf(SPConstants.AT) != -1) {
      for (String groupName : groupNames) {
        groups.add(Util.getGroupNameAtDomain(groupName.toLowerCase(), domain.toUpperCase()));
      }
      return groups;
    } else if (format.indexOf(SPConstants.BACKSLASH) != -1) {
      for (String groupName : groupNames) {
        groups.add(Util.getGroupNameWithDomain(groupName.toLowerCase(), domain.toUpperCase()));
      }
      return groups;
    } else {
      for (String groupName : groupNames) {
        groups.add(groupName.toLowerCase());
      }
      return groups;
    }
  }

  /**
   * Returns the search user name after changing its format to the user name
   * format specified by the connector administrator during connector
   * configuration.
   *
   * @param userName
   */
  String addUserNameFormatForTheSearchUser(final String userName) {
    String format = this.sharepointClientContext.getUsernameFormatInAce();
    LOGGER.config("Username format in ACE : " + format);
    String domain = this.sharepointClientContext.getDomain();
    if (format.indexOf(SPConstants.AT) != -1) {
      return Util.getUserNameAtDomain(userName, domain);
    } else if (format.indexOf(SPConstants.BACKSLASH) != -1) {
      return Util.getUserNameWithDomain(userName, domain);
    } else {
      return userName;
    }
  }

  /**
   * Create and returns a {@link ConcurrentHashMap} by querying LDAP directory
   * server to fetch all AD groups that the search user belongs to and then
   * queries User Data Store with a {@link Set}of AD groups and search user to
   * fetch all SP groups.
   *
   * @param searchUser the searchUser
   * @throws SharepointException
   */
  private ConcurrentHashMap<String, Set<Principal>>
      getAllADGroupsAndSPGroupsForSearchUser(String searchUser) {
    Set<String> adGroups = getAllLdapGroups(searchUser);
    Set<Principal> finalADGroups = new HashSet<Principal>();
    if (null != adGroups && adGroups.size() > 0) {
      String globalNamespace =
          sharepointClientContext.getGoogleGlobalNamespace();
      for (String adGroup : addGroupNameFormatForTheGroups(adGroups)) {
        finalADGroups.add(
            new Principal(PrincipalType.UNKNOWN, globalNamespace, adGroup));
      }
      finalADGroups.add(
          new Principal(PrincipalType.UNKNOWN, globalNamespace, "Everyone"));
      finalADGroups.add(new Principal(PrincipalType.UNKNOWN,
          globalNamespace, "NT AUTHORITY\\authenticated users"));
      finalADGroups.add(new Principal(PrincipalType.UNKNOWN,
          globalNamespace, "NT AUTHORITY\\interactive"));
    }
    String finalSearchUserName = addUserNameFormatForTheSearchUser(searchUser);
    LOGGER.info("Querying user data store with the AD groups: "
        + finalADGroups + " and search user [" + finalSearchUserName + "]");
    Set<Principal> spGroups = getAllSPGroupsForSearchUserAndLdapGroups(
        finalSearchUserName, finalADGroups);

    ConcurrentHashMap<String, Set<Principal>> userGroupsMap =
        new ConcurrentHashMap<String, Set<Principal>>(2);
    userGroupsMap.put(SPConstants.ADGROUPS, finalADGroups);
    userGroupsMap.put(SPConstants.SPGROUPS, spGroups);
    return userGroupsMap;
  }
}
TOP

Related Classes of com.google.enterprise.connector.sharepoint.ldap.UserGroupsService$LdapConnectionSettings

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.