001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import java.io.IOException;
021import java.net.HttpURLConnection;
022import java.net.URL;
023import org.apache.commons.codec.binary.Base64;
024import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
025import org.apache.directory.server.core.api.DirectoryService;
026import org.apache.directory.server.core.integ.ApacheDSTestExtension;
027import org.apache.directory.server.ldap.LdapServer;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.http.resource.JerseyResource;
030import org.junit.jupiter.api.AfterAll;
031import org.junit.jupiter.api.BeforeAll;
032import org.junit.jupiter.api.extension.ExtendWith;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Base class for setting up and testing an HTTP server with LDAP authentication.
038 */
039@ExtendWith(ApacheDSTestExtension.class)
040public class LdapServerTestBase extends HttpServerFunctionalTest {
041  private static final Logger LOG = LoggerFactory.getLogger(LdapServerTestBase.class);
042
043  protected static HttpServer server;
044  protected static URL baseUrl;
045
046  /**
047   * The following fields are set by ApacheDSTestExtension. These are normally inherited from
048   * AbstractLdapTestUnit, but this class already has a parent. We only use ldapServer, but
049   * declaring that one alone does not work.
050   */
051
052  /** The class DirectoryService instance */
053  public static DirectoryService classDirectoryService;
054
055  /** The test DirectoryService instance */
056  public static DirectoryService methodDirectoryService;
057
058  /** The current DirectoryService instance */
059  public static DirectoryService directoryService;
060
061  /** The class LdapServer instance */
062  public static LdapServer classLdapServer;
063
064  /** The test LdapServer instance */
065  public static LdapServer methodLdapServer;
066
067  /** The current LdapServer instance */
068  public static LdapServer ldapServer;
069
070  /** The Ldap connection template */
071  public static LdapConnectionTemplate ldapConnectionTemplate;
072
073  /** The current revision */
074  public static long revision = 0L;
075
076  /**
077   * End of fields required by ApacheDSTestExtension
078   */
079
080  private static final String AUTH_TYPE = "Basic ";
081
082  protected static LdapServer getLdapServer() {
083    return classLdapServer;
084  }
085
086  /**
087   * Sets up the HTTP server with LDAP authentication before any tests are run.
088   * @throws Exception if an error occurs during server setup
089   */
090  @BeforeAll
091  public static void setupServer() throws Exception {
092    Configuration conf = new Configuration();
093    setLdapConfigurations(conf);
094
095    server = createTestServer(conf);
096    server.addUnprivilegedServlet("echo", "/echo", TestHttpServer.EchoServlet.class);
097    server.addJerseyResourcePackage(JerseyResource.class.getPackage().getName(), "/jersey/*");
098    server.start();
099
100    baseUrl = getServerURL(server);
101    LOG.info("HTTP server started: " + baseUrl);
102  }
103
104  /**
105   * Stops the HTTP server after all tests are completed.
106   * @throws Exception if an error occurs during server shutdown
107   */
108  @AfterAll
109  public static void stopServer() throws Exception {
110    try {
111      if (null != server) {
112        server.stop();
113      }
114    } catch (Exception e) {
115      LOG.info("Failed to stop info server", e);
116    }
117  }
118
119  /**
120   * Configures the provided Configuration object for LDAP authentication.
121   * @param conf the Configuration object to set LDAP properties on
122   * @return the configured Configuration object
123   */
124  protected static void setLdapConfigurations(Configuration conf) {
125    conf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS);
126
127    // Enable LDAP (pre-req)
128    conf.set(HttpServer.HTTP_UI_AUTHENTICATION, "ldap");
129    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
130      "org.apache.hadoop.hbase.http.lib.AuthenticationFilterInitializer");
131    conf.set("hadoop.http.authentication.type", "ldap");
132    conf.set("hadoop.http.authentication.ldap.providerurl",
133      String.format("ldap://%s:%s", LdapConstants.LDAP_SERVER_ADDR, getLdapServer().getPort()));
134    conf.set("hadoop.http.authentication.ldap.enablestarttls", "false");
135    conf.set("hadoop.http.authentication.ldap.basedn", LdapConstants.LDAP_BASE_DN);
136  }
137
138  /**
139   * Generates a Basic Authentication header from the provided credentials.
140   * @param credentials the credentials to encode
141   * @return the Basic Authentication header
142   */
143  private String getBasicAuthHeader(String credentials) {
144    return AUTH_TYPE + new Base64(0).encodeToString(credentials.getBytes());
145  }
146
147  /**
148   * Opens an HTTP connection to the specified endpoint with optional Basic Authentication.
149   * @param endpoint    the endpoint to connect to
150   * @param credentials the credentials for Basic Authentication (optional)
151   * @return the opened HttpURLConnection
152   * @throws IOException if an error occurs while opening the connection
153   */
154  protected HttpURLConnection openConnection(String endpoint, String credentials)
155    throws IOException {
156    URL url = new URL(getServerURL(server) + endpoint);
157    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
158    if (credentials != null) {
159      conn.setRequestProperty("Authorization", getBasicAuthHeader(credentials));
160    }
161    return conn;
162  }
163}