content: /*
   * 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 org.apache.ambari.server.ldap.service.ads;
  
  import static org.apache.ambari.server.configuration.AmbariServerConfigurationCategory.LDAP_CONFIGURATION;
  
  import java.util.List;
  import java.util.Map;
  
  import jakarta.inject.Inject;
  import jakarta.inject.Singleton;
  
  import org.apache.ambari.server.configuration.AmbariServerConfigurationKey;
  import org.apache.ambari.server.ldap.domain.AmbariLdapConfiguration;
  import org.apache.ambari.server.ldap.service.AmbariLdapException;
  import org.apache.ambari.server.ldap.service.AttributeDetector;
  import org.apache.ambari.server.ldap.service.LdapAttributeDetectionService;
  import org.apache.ambari.server.ldap.service.ads.detectors.AttributeDetectorFactory;
  import org.apache.directory.api.ldap.model.entry.Entry;
  import org.apache.directory.api.ldap.model.exception.LdapException;
  import org.apache.directory.api.ldap.model.message.SearchRequest;
  import org.apache.directory.api.ldap.model.message.SearchScope;
  import org.apache.directory.api.util.Strings;
  import org.apache.directory.ldap.client.api.search.FilterBuilder;
  import org.apache.directory.ldap.client.template.EntryMapper;
  import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Service implementation that performs user and group attribute detection based on a sample set of entries returned by
   * an ldap search operation. A accuracy of detected values may depend on the size of the sample result set
   */
  @Singleton
  public class DefaultLdapAttributeDetectionService implements LdapAttributeDetectionService {
  
    private static final Logger LOG = LoggerFactory.getLogger(DefaultLdapAttributeDetectionService.class);
  
    /**
     * The maximum size of the entry set the detection is performed on
     */
    private static final int SAMPLE_RESULT_SIZE = 50;
  
    @Inject
    private AttributeDetectorFactory attributeDetectorFactory;
  
    @Inject
    private LdapConnectionTemplateFactory ldapConnectionTemplateFactory;
  
    @Inject
    public DefaultLdapAttributeDetectionService() {
    }
  
    @Override
    public AmbariLdapConfiguration detectLdapUserAttributes(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      LOG.info("Detecting LDAP user attributes ...");
  
      // perform a search using the user search base
      if (Strings.isEmpty(ambariLdapConfiguration.userSearchBase())) {
        LOG.warn("No user search base provided");
        return ambariLdapConfiguration;
      }
  
      try {
  
        LdapConnectionTemplate ldapConnectionTemplate = ldapConnectionTemplateFactory.create(ambariLdapConfiguration);
        AttributeDetector<Entry> userAttributeDetector = attributeDetectorFactory.userAttributDetector();
  
        SearchRequest searchRequest = assembleUserSearchRequest(ldapConnectionTemplate, ambariLdapConfiguration);
  
        // do the search
        List<Entry> entries = ldapConnectionTemplate.search(searchRequest, getEntryMapper());
  
        for (Entry entry : entries) {
          LOG.info("Collecting user attribute information from the sample entry with dn: [{}]", entry.getDn());
          userAttributeDetector.collect(entry);
        }
  
        // select attributes based on the collected information
        Map<String, String> detectedUserAttributes = userAttributeDetector.detect();
  
        // setting the attributes into the configuration
        setDetectedAttributes(ambariLdapConfiguration, detectedUserAttributes);
  
        LOG.info("Decorated ambari ldap config : [{}]", ambariLdapConfiguration);
  
      } catch (Exception e) {
  
        LOG.error("Ldap operation failed while detecting user attributes", e);
        throw new AmbariLdapException(e);
  
      }
  
      return ambariLdapConfiguration;
    }
  
  
    @Override
    public AmbariLdapConfiguration detectLdapGroupAttributes(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      LOG.info("Detecting LDAP group attributes ...");
  
      // perform a search using the user search base
      if (Strings.isEmpty(ambariLdapConfiguration.groupSearchBase())) {
        LOG.warn("No group search base provided");
        return ambariLdapConfiguration;
      }
  
      try {
  
        LdapConnectionTemplate ldapConnectionTemplate = ldapConnectionTemplateFactory.create(ambariLdapConfiguration);
        AttributeDetector<Entry> groupAttributeDetector = attributeDetectorFactory.groupAttributeDetector();
  
        SearchRequest searchRequest = assembleGroupSearchRequest(ldapConnectionTemplate, ambariLdapConfiguration);
  
        // do the search
        List<Entry> groupEntries = ldapConnectionTemplate.search(searchRequest, getEntryMapper());
  
        for (Entry groupEntry : groupEntries) {
  
          LOG.info("Collecting group attribute information from the sample entry with dn: [{}]", groupEntry.getDn());
          groupAttributeDetector.collect(groupEntry);
  
        }
  
        // select attributes based on the collected information
        Map<String, String> detectedGroupAttributes = groupAttributeDetector.detect();
  
        // setting the attributes into the configuration
        setDetectedAttributes(ambariLdapConfiguration, detectedGroupAttributes);
  
        LOG.info("Decorated ambari ldap config : [{}]", ambariLdapConfiguration);
  
      } catch (Exception e) {
  
        LOG.error("Ldap operation failed while detecting group attributes", e);
        throw new AmbariLdapException(e);
  
      }
  
      return ambariLdapConfiguration;
    }
  
    private void setDetectedAttributes(AmbariLdapConfiguration ambariLdapConfiguration, Map<String, String> detectedAttributes) {
  
      for (Map.Entry<String, String> detecteMapEntry : detectedAttributes.entrySet()) {
        LOG.info("Setting detected configuration value: [{}] - > [{}]", detecteMapEntry.getKey(), detecteMapEntry.getValue());
        AmbariServerConfigurationKey key = AmbariServerConfigurationKey.translate(LDAP_CONFIGURATION, detecteMapEntry.getKey());
        if(key != null) {
          ambariLdapConfiguration.setValueFor(key, detecteMapEntry.getValue());
        }
      }
  
    }
  
    private SearchRequest assembleUserSearchRequest(LdapConnectionTemplate ldapConnectionTemplate, AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      try {
  
        SearchRequest req = ldapConnectionTemplate.newSearchRequest(ambariLdapConfiguration.userSearchBase(),
          FilterBuilder.present("objectClass").toString(), SearchScope.SUBTREE);
        req.setSizeLimit(SAMPLE_RESULT_SIZE);
  
        return req;
  
      } catch (Exception e) {
        LOG.error("Could not assemble ldap search request", e);
        throw new AmbariLdapException(e);
      }
    }
  
    private SearchRequest assembleGroupSearchRequest(LdapConnectionTemplate ldapConnectionTemplate, AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      try {
  
        SearchRequest req = ldapConnectionTemplate.newSearchRequest(ambariLdapConfiguration.groupSearchBase(),
          FilterBuilder.present("objectClass").toString(), SearchScope.SUBTREE);
        req.setSizeLimit(SAMPLE_RESULT_SIZE);
  
        return req;
  
      } catch (Exception e) {
        LOG.error("Could not assemble ldap search request", e);
        throw new AmbariLdapException(e);
      }
    }
  
    public EntryMapper<Entry> getEntryMapper() {
      return new EntryMapper<Entry>() {
        @Override
        public Entry map(Entry entry) throws LdapException {
          return entry;
        }
      };
    }
  }
  