content: /*
   * 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.calcite.avatica;
  
  import org.apache.calcite.avatica.remote.KerberosConnection;
  import org.apache.calcite.avatica.remote.Service.RpcMetadataResponse;
  import org.apache.calcite.avatica.server.AvaticaHandler;
  import org.apache.calcite.avatica.util.SecurityUtils;
  
  import org.apache.kerby.kerberos.kerb.KrbException;
  import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
  
  import org.eclipse.jetty.security.UserAuthentication;
  import org.eclipse.jetty.server.Authentication;
  import org.eclipse.jetty.server.Handler;
  import org.eclipse.jetty.server.Request;
  import org.eclipse.jetty.server.Server;
  import org.eclipse.jetty.server.UserIdentity;
  import org.eclipse.jetty.server.handler.DefaultHandler;
  
  import java.io.BufferedWriter;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStreamWriter;
  import java.net.ServerSocket;
  import java.nio.charset.StandardCharsets;
  import java.security.Principal;
  import java.security.PrivilegedAction;
  import javax.security.auth.login.Configuration;
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  /**
   * Utility class for setting up SPNEGO
   */
  public class SpnegoTestUtil {
  
    public static final String JGSS_KERBEROS_TICKET_OID = "1.2.840.113554.1.2.2";
  
    public static final String REALM = "EXAMPLE.COM";
    public static final String KDC_HOST = "localhost";
    public static final String CLIENT_NAME = "client";
    public static final String CLIENT_PRINCIPAL = CLIENT_NAME + "@" + REALM;
    public static final String SERVER_PRINCIPAL = "HTTP/" + KDC_HOST + "@" + REALM;
  
    private static final String TARGET_DIR_NAME = System.getProperty("target.dir", "target");
    public static final File TARGET_DIR = new File(System.getProperty("user.dir"), TARGET_DIR_NAME);
  
    private SpnegoTestUtil() {}
  
    public static int getFreePort() throws IOException {
      ServerSocket s = new ServerSocket(0);
      try {
        s.setReuseAddress(true);
        int port = s.getLocalPort();
        return port;
      } finally {
        if (null != s) {
          s.close();
        }
      }
    }
  
    public static void setupUser(SimpleKdcServer kdc, File keytab, String principal)
        throws KrbException {
      kdc.createPrincipal(principal);
      kdc.exportPrincipal(principal, keytab);
    }
  
    /**
     * Recursively deletes a {@link File}.
     */
    public static void deleteRecursively(File d) {
      if (d.isDirectory()) {
        for (String name : d.list()) {
          File child = new File(d, name);
          if (child.isFile()) {
            child.delete();
          } else {
            deleteRecursively(d);
          }
        }
      }
      d.delete();
    }
  
    /**
     * Creates the SPNEGO JAAS configuration file for the Jetty server
     */
    public static void writeSpnegoConf(File configFile, File serverKeytab)
        throws Exception {
      try (BufferedWriter writer =
               new BufferedWriter(
                   new OutputStreamWriter(
                       new FileOutputStream(configFile),
                       StandardCharsets.UTF_8))) {
        // Server login
        if (KerberosConnection.isIbmJava()) {
          writer.write("com.ibm.security.jgss.accept {\n");
        } else {
          writer.write("com.sun.security.jgss.accept {\n");
        }
        writer.write(" " + KerberosConnection.getKrb5LoginModuleName() + " required\n");
        writer.write(" principal=\"" + SERVER_PRINCIPAL + "\"\n");
        // Some extra debug information from JAAS
        // writer.write(" debug=true\n");
        if (KerberosConnection.isIbmJava()) {
          writer.write(" useKeytab=\"" + serverKeytab.getPath() + "\"\n");
          writer.write(" credsType=both;\n");
        } else {
          writer.write(" useKeyTab=true\n");
          writer.write(" keyTab=\"" + serverKeytab.getPath() + "\"\n");
          writer.write(" isInitiator=false\n");
          writer.write(" storeKey=true;\n");
        }
        writer.write("};\n");
      }
    }
  
    public static void refreshJaasConfiguration() {
      // This is *extremely* important to make sure we get the right Configuration instance.
      // Configuration keeps a static instance of Configuration that it will return once it
      // has been initialized. We need to nuke that static instance to make sure our
      // serverSpnegoConfigFile gets read.
      SecurityUtils.doPrivileged(new PrivilegedAction<Configuration>() {
        @Override
      public Configuration run() {
          return Configuration.getConfiguration();
        }
      }).refresh();
    }
  
    /**
     * A simple handler which returns "OK " with the client's authenticated name and HTTP/200 or
     * HTTP/401 and the message "Not authenticated!".
     */
    public static class AuthenticationRequiredAvaticaHandler implements AvaticaHandler {
      private final Handler handler = new DefaultHandler();
  
      @Override public void handle(String target, Request baseRequest, HttpServletRequest request,
          HttpServletResponse response) throws IOException, ServletException {
        Authentication auth = baseRequest.getAuthentication();
        if (Authentication.UNAUTHENTICATED == auth) {
          throw new AssertionError("Unauthenticated users should not reach here!");
        }
  
        baseRequest.setHandled(true);
        UserAuthentication userAuth = (UserAuthentication) auth;
        UserIdentity userIdentity = userAuth.getUserIdentity();
        Principal userPrincipal = userIdentity.getUserPrincipal();
  
        response.getWriter().print("OK " + userPrincipal.getName());
        response.setStatus(200);
      }
  
      @Override public void setServer(Server server) {
        handler.setServer(server);
      }
  
      @Override public Server getServer() {
        return handler.getServer();
      }
  
      @Override public void destroy() {
        handler.destroy();
      }
  
      @Override public void start() throws Exception {
        handler.start();
      }
  
      @Override public void stop() throws Exception {
        handler.stop();
      }
  
      @Override public boolean isRunning() {
        return handler.isRunning();
      }
  
      @Override public boolean isStarted() {
        return handler.isStarted();
      }
  
      @Override public boolean isStarting() {
        return handler.isStarting();
      }
  
      @Override public boolean isStopping() {
        return handler.isStopping();
      }
  
      @Override public boolean isStopped() {
        return handler.isStopped();
      }
  
      @Override public boolean isFailed() {
        return handler.isFailed();
      }
  
      @Override public void addLifeCycleListener(Listener listener) {
        handler.addLifeCycleListener(listener);
      }
  
      @Override public void removeLifeCycleListener(Listener listener) {
        handler.removeLifeCycleListener(listener);
      }
  
      @Override public void setServerRpcMetadata(RpcMetadataResponse metadata) {}
    }
  }
  
  // End SpnegoTestUtil.java
  