All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hive.jdbc.saml.HiveJdbcBrowserClient Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.hive.jdbc.saml;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import org.apache.hive.jdbc.Utils.JdbcConnectionParams;
import org.apache.hive.service.auth.saml.HiveSamlUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is used to execute a browser based SSO workflow with the authentication mode
 * is browser.
 */
public class HiveJdbcBrowserClient implements IJdbcBrowserClient {

  private static final Logger LOG = LoggerFactory.getLogger(HiveJdbcBrowserClient.class);
  // error message when the socket times out.
  @VisibleForTesting
  public static final String TIMEOUT_ERROR_MSG = "Timed out while waiting for server response";
  // port as parsed from the connection URL; default is 0 which means any available port.
  private final int portFromUrl;
  // the actual port on the local machine where the web server is running
  private Integer serverPort;
  // timeout in mill-sec until which browserClient will wait for auth response from the
  // HS2 server.
  private final long timeoutInMs;
  private final BlockingQueue
      serverResponseQueue = new LinkedBlockingDeque<>();
  protected JdbcBrowserClientContext clientContext;
  // By default we wait for 2 min unless overridden by a JDBC connection param
  // browserResponseTimeout
  private static final int DEFAULT_SOCKET_TIMEOUT_SECS = 120;
  private Server webServer;
  private HiveJdbcBrowserServerResponse serverResponse;

  HiveJdbcBrowserClient(JdbcConnectionParams connectionParams)
      throws HiveJdbcBrowserException {
    portFromUrl = Integer.parseInt(connectionParams.getSessionVars()
        .getOrDefault(JdbcConnectionParams.AUTH_BROWSER_RESPONSE_PORT, "0"));
    timeoutInMs = Integer.parseInt(
        connectionParams.getSessionVars()
            .getOrDefault(JdbcConnectionParams.AUTH_BROWSER_RESPONSE_TIMEOUT_SECS,
                String.valueOf(DEFAULT_SOCKET_TIMEOUT_SECS))) * 1000L;
  }

  @Override
  public void startListening() throws HiveJdbcBrowserException {
    webServer = new Server();
    ServerConnector serverConnector = new ServerConnector(webServer);
    serverConnector.setHost(HiveSamlUtils.LOOP_BACK_INTERFACE);
    serverConnector.setPort(portFromUrl);
    LOG.info("Browser response timeout is set to {} ms", timeoutInMs);
    serverConnector.setIdleTimeout(timeoutInMs);
    webServer.addConnector(serverConnector);
    ServletHandler servletHandler = new ServletHandler();
    servletHandler.addServletWithMapping(
        new ServletHolder(new HttpBrowserClientServlet(this)), "/");
    webServer.setHandler(servletHandler);
    webServer.setStopTimeout(30*1000L);
    try {
      webServer.start();
      // we fetch the port after the server is started so that we can get the port
      // where the server has bound in case there is no port specified from the URL.
      serverPort = ((ServerConnector) webServer.getConnectors()[0]).getLocalPort();
      LOG.debug("Listening on the port {} ", serverPort);
    } catch (Exception e) {
      throw new HiveJdbcBrowserException("Could not start http server", e);
    }
  }

  public Integer getPort() {
    return serverPort;
  }

  @Override
  public String toString() {
    return "HiveJdbcBrowserClient@" + serverPort;
  }

  @Override
  public HiveJdbcBrowserServerResponse getServerResponse() {
    return serverResponse;
  }

  @Override
  public void close() throws IOException {
    if (webServer != null && webServer.isRunning()) {
      try {
        webServer.stop();
      } catch (Exception e) {
        throw new IOException(e);
      }
      webServer = null;
    }
  }

  public void init(JdbcBrowserClientContext clientContext) {
    // everytime we set the sso URI we should clean up the previous state if its set.
    // this may be from the previous invalid connection attempt or if the token has
    // expired
    reset();
    this.clientContext = clientContext;
    LOG.debug("Initialized the JDBCBrowser client with URL {}",
        clientContext.getSsoUri());
  }

  private void reset() {
    serverResponse = null;
    clientContext = null;
  }

  public void doBrowserSSO() throws HiveJdbcBrowserException {
    logDebugInfoUri(clientContext.getSsoUri());
    openBrowserWindow();
    try {
      waitForServerResponse(timeoutInMs);
    } catch (InterruptedException e) {
      throw new HiveJdbcBrowserException(e);
    }
    if (serverResponse == null) {
      throw new HiveJdbcBrowserException(TIMEOUT_ERROR_MSG);
    }
    if (!serverResponse.isValid()) {
      throw new HiveJdbcBrowserException(
          "Received invalid response from server. See driver logs for more details");
    }
  }

  private void logDebugInfoUri(URI ssoURI) {
    Map uriParams = new HashMap<>();
    try {
      uriParams = getQueryParams(ssoURI);
    } catch (HiveJdbcBrowserException e) {
      LOG.info("Could get query params of the SSO URI", e);
    }
    LOG.debug("Initializing browser SSO request to URI. Relay state is {}",
        uriParams.get("RelayState"));
  }

  private Map getQueryParams(URI ssoUri)
      throws HiveJdbcBrowserException {
    String decodedUrl;
    try {
      decodedUrl = URLDecoder
          .decode(ssoUri.toString(), StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new HiveJdbcBrowserException(e);
    }
    String[] params;
    try {
      params = new URI(decodedUrl).getQuery().split("&");
    } catch (URISyntaxException e) {
      throw new HiveJdbcBrowserException(e);
    }
    Map paramMap = new HashMap<>();
    for (String param : params) {
      String key = param.split("=")[0];
      String val = param.split("=")[1];
      paramMap.put(key, val);
    }
    return paramMap;
  }

  @VisibleForTesting
  protected void openBrowserWindow() throws HiveJdbcBrowserException {
    URI ssoUri = clientContext.getSsoUri();
    Preconditions.checkNotNull(ssoUri, "SSO Url is null");
    try {
      if (Desktop.isDesktopSupported() && Desktop.getDesktop()
          .isSupported(Action.BROWSE)) {
        Desktop.getDesktop().browse(ssoUri);
      } else {
        LOG.info(
            "Desktop mode is not supported. Attempting to use OS "
                + "commands to open the default browser");
        //Desktop is not supported, lets try to open the browser process
        OsType os = getOperatingSystem();
        switch (os) {
          case WINDOWS:
            Runtime.getRuntime()
                .exec("rundll32 url.dll,FileProtocolHandler " + ssoUri.toString());
            break;
          case MAC:
            Runtime.getRuntime().exec("open " + ssoUri.toString());
            break;
          case LINUX:
            Runtime.getRuntime().exec("xdg-open " + ssoUri.toString());
            break;
          case UNKNOWN:
            throw new HiveJdbcBrowserException(
                "Unknown operating system " + System.getProperty("os.name"));
        }
      }
    } catch (IOException e) {
      throw new HiveJdbcBrowserException("Unable to open browser to execute SSO", e);
    }
  }

  public void addServerResponse(HiveJdbcBrowserServerResponse response) {
    serverResponseQueue.add(response);
  }

  /**
   * Waits for a server response until the given timeout value in milliseconds.
   * Returns null if timeout.
   */
  private void waitForServerResponse(long timeoutInMs)
      throws InterruptedException {
     serverResponse = serverResponseQueue.poll(timeoutInMs, TimeUnit.MILLISECONDS);
  }

  public String getClientIdentifier() {
    if (clientContext == null) {
      return null;
    }
    return clientContext.getClientIdentifier();
  }

  private void sendBrowserMsg(Socket socket, boolean success) throws IOException {
    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

    List content = new ArrayList<>();
    content.add("HTTP/1.0 200 OK");
    content.add("Content-Type: text/html");
    String responseText;
    if (success) {
      responseText =
          ""
              + "SAML Response Received"
                  + "Successfully authenticated. You may close this window." +
                  ""+"";
    } else {
      responseText =
          ""
              + "SAML Response Received"
              + "Authentication failed. Please check server logs for details."
              + " You may close this window.";
    }
    content.add(String.format("Content-Length: %s", responseText.length()));
    content.add("");
    content.add(responseText);

    for (int i = 0; i < content.size(); ++i) {
      if (i > 0) {
        out.print("\r\n");
      }
      out.print(content.get(i));
    }
    out.flush();
  }

  public OsType getMatchingOs(String osName) {
    osName = osName.toLowerCase();
    if (osName.contains("win")) {
      return OsType.WINDOWS;
    }
    if (osName.contains("mac")) {
      return OsType.MAC;
    }
    if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
      return OsType.LINUX;
    }
    return OsType.UNKNOWN;
  }

  private enum OsType {
    WINDOWS,
    MAC,
    LINUX,
    UNKNOWN
  }

  private OsType getOperatingSystem() {
    String osName = System.getProperty("os.name");
    Preconditions.checkNotNull(osName, "os.name is null");
    return getMatchingOs(osName);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy