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 java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import jakarta.inject.Inject;
  import jakarta.inject.Singleton;
  
  import org.apache.ambari.server.ldap.domain.AmbariLdapConfiguration;
  import org.apache.ambari.server.ldap.service.AmbariLdapException;
  import org.apache.ambari.server.ldap.service.LdapConfigurationService;
  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
  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.ldap.model.name.Dn;
  import org.apache.directory.ldap.client.api.LdapConnection;
  import org.apache.directory.ldap.client.api.search.FilterBuilder;
  import org.apache.directory.ldap.client.template.ConnectionCallback;
  import org.apache.directory.ldap.client.template.EntryMapper;
  import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Implementation of the validation logic using the Apache Directory API.
   */
  @Singleton
  public class DefaultLdapConfigurationService implements LdapConfigurationService {
  
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLdapConfigurationService.class);
  
    @Inject
    private LdapConnectionTemplateFactory ldapConnectionTemplateFactory;
  
    @Inject
    public DefaultLdapConfigurationService() {
    }
  
  
    @Override
    public void checkConnection(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      LOGGER.info("Trying to connect to the LDAP server using provided configuration...");
      LdapConnectionTemplate ldapConnectionTemplate = ldapConnectionTemplateFactory.create(ambariLdapConfiguration);
  
      // check if the connection from the connection pool of the template is connected
      Boolean isConnected = ldapConnectionTemplate.execute(new ConnectionCallback<Boolean>() {
        @Override
        public Boolean doWithConnection(LdapConnection connection) throws LdapException {
          return connection.isConnected() && connection.isAuthenticated();
        }
      });
  
      if (!isConnected) {
        LOGGER.error("Could not connect to the LDAP server");
        throw new AmbariLdapException("Could not connect to the LDAP server. Configuration: " + ambariLdapConfiguration);
      }
  
      LOGGER.info("Successfully conencted to the LDAP.");
  
    }
  
    /**
     * Checks the user attributes provided in the configuration instance by issuing a search for a (known) test user in the LDAP.
     * Attributes are considered correct if there is at least one entry found.
     *
     * Invalid attributes are signaled by throwing an exception.
     *
     * @param testUserName            the test username
     * @param testPassword            the test password
     * @param ambariLdapConfiguration the available LDAP configuration to be validated
     * @return the DN of the test user
     * @throws AmbariLdapException if an error occurs
     */
    @Override
    public String checkUserAttributes(String testUserName, String testPassword, AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      String userDn;
      try {
        LOGGER.info("Checking user attributes for user [{}] ...", testUserName);
  
        // set up a filter based on the provided attributes
        String filter = FilterBuilder.and(
          FilterBuilder.equal(SchemaConstants.OBJECT_CLASS_AT, ambariLdapConfiguration.userObjectClass()),
          FilterBuilder.equal(ambariLdapConfiguration.userNameAttribute(), testUserName))
          .toString();
  
        LOGGER.info("Searching for the user: [{}] using the search filter: [{}]", testUserName, filter);
        userDn = ldapConnectionTemplateFactory.create(ambariLdapConfiguration).searchFirst(new Dn(ambariLdapConfiguration.userSearchBase()), filter, SearchScope.SUBTREE, getUserDnNameEntryMapper(ambariLdapConfiguration));
  
        if (null == userDn) {
          LOGGER.info("Could not find test user based on the provided configuration. User attributes may not be complete or the user may not exist.");
          throw new AmbariLdapException("Could not find test user based on the provided configuration. User attributes may not be complete or the user may not exist.");
        }
        LOGGER.info("Attribute validation succeeded. Filter: [{}]", filter);
  
  
      } catch (Exception e) {
  
        LOGGER.error("User attributes validation failed.", e);
        throw new AmbariLdapException(e.getMessage(), e);
  
      }
      return userDn;
    }
  
    /**
     * Checks whether the provided group related settings are correct.
     *
     * @param userDn                  a user DN to check
     * @param ambariLdapConfiguration the available LDAP configuration to be validated
     * @return
     * @throws AmbariLdapException
     */
    @Override
    public Set<String> checkGroupAttributes(String userDn, AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
      List<String> groups;
      try {
        LOGGER.info("Checking group attributes for user dn: [{}] ...", userDn);
  
        // set up a filter based on the provided attributes
        String filter = FilterBuilder.and(
          FilterBuilder.equal(SchemaConstants.OBJECT_CLASS_AT, ambariLdapConfiguration.groupObjectClass()),
          FilterBuilder.equal(ambariLdapConfiguration.groupMemberAttribute(), userDn)
        ).toString();
  
        LOGGER.info("Searching for the groups the user dn: [{}] is member of using the search filter: [{}]", userDn, filter);
        LdapConnectionTemplate ldapConnectionTemplate = ldapConnectionTemplateFactory.create(ambariLdapConfiguration);
  
        // assemble a search request
        SearchRequest searchRequest = ldapConnectionTemplate.newSearchRequest(new Dn(ambariLdapConfiguration.groupSearchBase()), filter, SearchScope.SUBTREE);
        // attributes to be returned
        searchRequest.addAttributes(ambariLdapConfiguration.groupMemberAttribute(), ambariLdapConfiguration.groupNameAttribute());
  
        // perform the search
        groups = ldapConnectionTemplate.search(searchRequest, getGroupNameEntryMapper(ambariLdapConfiguration));
  
        if (groups == null || groups.isEmpty()) {
          LOGGER.info("No groups found for the user dn. Group attributes configuration is incomplete");
          throw new AmbariLdapException("Group attribute ldap configuration is incomplete");
        }
  
        LOGGER.info("Group attribute configuration check succeeded.");
  
      } catch (Exception e) {
  
        LOGGER.error("User attributes validation failed.", e);
        throw new AmbariLdapException(e.getMessage(), e);
  
      }
  
      return new HashSet<>(groups);
    }
  
  
    /**
     * Entry mapper for handling user search results.
     *
     * @param ambariLdapConfiguration ambari ldap configuration values
     * @return user dn entry mapper instance
     */
    private EntryMapper<String> getGroupNameEntryMapper(AmbariLdapConfiguration ambariLdapConfiguration) {
  
      EntryMapper<String> entryMapper = new EntryMapper<String>() {
        @Override
        public String map(Entry entry) throws LdapException {
          return entry.get(ambariLdapConfiguration.groupNameAttribute()).get().getValue();
        }
      };
  
      return entryMapper;
    }
  
    /**
     * Entry mapper for handling group searches.
     *
     * @param ambariLdapConfiguration ambari ldap configuration values
     * @return
     */
    private EntryMapper<String> getUserDnNameEntryMapper(AmbariLdapConfiguration ambariLdapConfiguration) {
  
      EntryMapper<String> entryMapper = new EntryMapper<String>() {
        @Override
        public String map(Entry entry) throws LdapException {
          return entry.getDn().getNormName();
        }
      };
  
      return entryMapper;
    }
  
  
  }
  
  
  
  