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

com.google.appengine.tools.development.jetty9.DevAppEngineWebAppContext Maven / Gradle / Ivy

/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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 com.google.appengine.tools.development.jetty9;

import com.google.appengine.tools.development.DevAppServer;
import com.google.appengine.tools.info.AppengineSdk;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.utils.io.IoUtil;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.resource.Resource;

/**
 * An AppEngineWebAppContext for the DevAppServer.
 *
 */
public class DevAppEngineWebAppContext extends AppEngineWebAppContext {

  private static final Logger logger =
      Logger.getLogger(DevAppEngineWebAppContext.class.getName());

  // Copied from org.apache.jasper.Constants.SERVLET_CLASSPATH
  // to remove compile-time dependency on Jasper
  private static final String JASPER_SERVLET_CLASSPATH = "org.apache.catalina.jsp_classpath";

  // Header that allows arbitrary requests to bypass jetty's security
  // mechanisms.  Useful for things like the dev task queue, which needs
  // to hit secure urls without an authenticated user.
  private static final String X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK =
      "X-Google-DevAppserver-SkipAdminCheck";

  // Keep in sync with com.google.apphosting.utils.jetty.AppEngineAuthentication.
  private static final String SKIP_ADMIN_CHECK_ATTR =
      "com.google.apphosting.internal.SkipAdminCheck";

  private final Object transportGuaranteeLock = new Object();
  private boolean transportGuaranteesDisabled = false;

  public DevAppEngineWebAppContext(File appDir, File externalResourceDir, String serverInfo,
      ApiProxy.Delegate apiProxyDelegate, DevAppServer devAppServer) {
    super(appDir, serverInfo);

    // Set up the classpath required to compile JSPs. This is specific to Jasper.
    setAttribute(JASPER_SERVLET_CLASSPATH, buildClasspath());

    // Make ApiProxyLocal available via the servlet context.  This allows
    // servlets that are part of the dev appserver (like those that render the
    // dev console for example) to get access to this resource even in the
    // presence of libraries that install their own custom Delegates (like
    // Remote api and Appstats for example).
    _scontext.setAttribute("com.google.appengine.devappserver.ApiProxyLocal", apiProxyDelegate);

    // Make the dev appserver available via the servlet context as well.
    _scontext.setAttribute("com.google.appengine.devappserver.Server", devAppServer);
  }

  /**
   * By default, the context is created with alias checkers for symlinks:
   * {@link org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker}.
   */
  @Override
  public boolean checkAlias(String path, Resource resource) {
    return true;
  }

  @Override
  public void doScope(
      String target,
      Request baseRequest,
      HttpServletRequest httpServletRequest,
      HttpServletResponse httpServletResponse)
      throws IOException, ServletException {

    if (hasSkipAdminCheck(baseRequest)) {
      baseRequest.setAttribute(SKIP_ADMIN_CHECK_ATTR, Boolean.TRUE);
    }

    disableTransportGuarantee();

    // TODO An extremely heinous way of helping the DevAppServer's
    // SecurityManager determine if a DevAppServer request thread is executing.
    // Find something better.
    // See DevAppServerFactory.CustomSecurityManager.
    System.setProperty("devappserver-thread-" + Thread.currentThread().getName(), "true");
    try {
      super.doScope(target, baseRequest, httpServletRequest, httpServletResponse);
    } finally {
      System.clearProperty("devappserver-thread-" + Thread.currentThread().getName());
    }
  }

  /**
   * Returns true if the X-Google-Internal-SkipAdminCheck header is
   * present.  There is nothing preventing usercode from setting this header
   * and circumventing dev appserver security, but the dev appserver was not
   * designed to be secure.
   */
  private boolean hasSkipAdminCheck(HttpServletRequest request) {
    // wow, old school java
    for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); ) {
      String name = (String) headerNames.nextElement();
      // We don't care about the header value, its presence is sufficient.
      if (name.equalsIgnoreCase(X_GOOGLE_DEV_APPSERVER_SKIPADMINCHECK)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Builds a classpath up for the webapp for JSP compilation.
   */
  private String buildClasspath() {
    StringBuilder classpath = new StringBuilder();

    // Shared servlet container classes
    for (File f : AppengineSdk.getSdk().getSharedLibFiles()) {
      classpath.append(f.getAbsolutePath());
      classpath.append(File.pathSeparatorChar);
    }

    String webAppPath = getWar();

    // webapp classes
    classpath.append(webAppPath + File.separator + "classes" + File.pathSeparatorChar);

    List files = IoUtil.getFilesAndDirectories(new File(webAppPath, "lib"));
    for (File f : files) {
      if (f.isFile() && f.getName().endsWith(".jar")) {
        classpath.append(f.getAbsolutePath());
        classpath.append(File.pathSeparatorChar);
      }
    }

    return classpath.toString();
  }

  /**
   * The first time this method is called it will walk through the
   * constraint mappings on the current SecurityHandler and disable
   * any transport guarantees that have been set.  This is required to
   * disable SSL requirements in the DevAppServer because it does not
   * support SSL.
   */
  private void disableTransportGuarantee() {
    synchronized (transportGuaranteeLock) {
      if (!transportGuaranteesDisabled && getSecurityHandler() != null) {
        List mappings =
            ((ConstraintSecurityHandler) getSecurityHandler()).getConstraintMappings();
        if (mappings != null) {
          for (ConstraintMapping mapping : mappings) {
            if (mapping.getConstraint().getDataConstraint() > 0) {
              logger.info(
                  "Ignoring  for "
                      + mapping.getPathSpec()
                      + " as the SDK does not support HTTPS.  It will still be used"
                      + " when you upload your application.");
              mapping.getConstraint().setDataConstraint(0);
            }
          }
        }
      }
      transportGuaranteesDisabled = true;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy