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

com.google.gerrit.httpd.rpc.GerritJsonServlet Maven / Gradle / Ivy

// Copyright (C) 2008 The Android Open Source Project
//
// 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 com.google.gerrit.httpd.rpc;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.audit.RpcAuditEvent;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.audit.Audit;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gson.GsonBuilder;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.server.ActiveCall;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.gwtjsonrpc.server.MethodHandle;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Base JSON servlet to ensure the current user is not forged.
 */
@SuppressWarnings("serial")
final class GerritJsonServlet extends JsonServlet {
  private static final Logger log = LoggerFactory.getLogger(GerritJsonServlet.class);
  private static final ThreadLocal currentCall =
      new ThreadLocal<>();
  private static final ThreadLocal currentMethod =
      new ThreadLocal<>();
  private final DynamicItem session;
  private final RemoteJsonService service;
  private final AuditService audit;


  @Inject
  GerritJsonServlet(final DynamicItem w, final RemoteJsonService s,
      final AuditService a) {
    session = w;
    service = s;
    audit = a;
  }

  @Override
  protected GerritCall createActiveCall(final HttpServletRequest req,
      final HttpServletResponse rsp) {
    final GerritCall call = new GerritCall(session.get(), req, new AuditedHttpServletResponse(rsp));
    currentCall.set(call);
    return call;
  }

  @Override
  protected GsonBuilder createGsonBuilder() {
    return gerritDefaultGsonBuilder();
  }

  private static GsonBuilder gerritDefaultGsonBuilder() {
    final GsonBuilder g = defaultGsonBuilder();

    g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
        new org.eclipse.jgit.diff.EditDeserializer());

    return g;
  }

  @Override
  protected void preInvoke(final GerritCall call) {
    super.preInvoke(call);

    if (call.isComplete()) {
      return;
    }

    if (call.getMethod().getAnnotation(SignInRequired.class) != null) {
      // If SignInRequired is set on this method we must have both a
      // valid XSRF token *and* have the user signed in. Doing these
      // checks also validates that they agree on the user identity.
      //
      if (!call.requireXsrfValid() || !session.get().isSignedIn()) {
        call.onFailure(new NotSignedInException());
      }
    }
  }

  @Override
  protected Object createServiceHandle() {
    return service;
  }

  @Override
  protected void service(final HttpServletRequest req,
      final HttpServletResponse resp) throws IOException {
    try {
      super.service(req, resp);
    } finally {
      audit();
      currentCall.set(null);
    }
  }

  private void audit() {
    try {
      GerritCall call = currentCall.get();
      MethodHandle method = call.getMethod();
      if (method == null) {
        return;
      }
      Audit note = method.getAnnotation(Audit.class);
      if (note != null) {
        final String sid = call.getWebSession().getSessionId();
        final CurrentUser username = call.getWebSession().getUser();
        final Multimap args =
            extractParams(note, call);
        final String what = extractWhat(note, call);
        final Object result = call.getResult();

        audit.dispatch(new RpcAuditEvent(sid, username, what, call.getWhen(),
            args, call.getHttpServletRequest().getMethod(), call.getHttpServletRequest().getMethod(),
            ((AuditedHttpServletResponse) (call.getHttpServletResponse()))
                .getStatus(), result));
      }
    } catch (Throwable all) {
      log.error("Unable to log the call", all);
    }
  }

  private Multimap extractParams(final Audit note, final GerritCall call) {
    Multimap args = ArrayListMultimap.create();

    Object[] params = call.getParams();
    for (int i = 0; i < params.length; i++) {
      args.put("$" + i, params[i]);
    }

    for (int idx : note.obfuscate()) {
      args.removeAll("$" + idx);
      args.put("$" + idx, "*****");
    }
    return args;
  }

  private String extractWhat(final Audit note, final GerritCall call) {
    Class methodClass = call.getMethodClass();
    String methodClassName = methodClass != null
        ? methodClass.getName()
        : "";
    methodClassName =
        methodClassName.substring(methodClassName.lastIndexOf(".") + 1);
    String what = note.action();
    if (what.length() == 0) {
      what = call.getMethod().getName();
    }

    return methodClassName + "." + what;
  }

  static class GerritCall extends ActiveCall {
    private final WebSession session;
    private final long when;
    private static final Field resultField;
    private static final Field methodField;

    // Needed to allow access to non-public result field in GWT/JSON-RPC
    static {
      resultField = getPrivateField(ActiveCall.class, "result");
      methodField = getPrivateField(MethodHandle.class, "method");
    }

    private static Field getPrivateField(Class clazz, String fieldName) {
      Field declaredField = null;
      try {
        declaredField = clazz.getDeclaredField(fieldName);
        declaredField.setAccessible(true);
      } catch (Exception e) {
        log.error("Unable to expose RPS/JSON result field");
      }
      return declaredField;
    }

    // Surrogate of the missing getMethodClass() in GWT/JSON-RPC
    public Class getMethodClass() {
      if (methodField == null) {
        return null;
      }

      try {
        Method method = (Method) methodField.get(this.getMethod());
        return method.getDeclaringClass();
      } catch (IllegalArgumentException e) {
        log.error("Cannot access result field");
      } catch (IllegalAccessException e) {
        log.error("No permissions to access result field");
      }

      return null;
    }

    // Surrogate of the missing getResult() in GWT/JSON-RPC
    public Object getResult() {
      if (resultField == null) {
        return null;
      }

      try {
        return resultField.get(this);
      } catch (IllegalArgumentException e) {
        log.error("Cannot access result field");
      } catch (IllegalAccessException e) {
        log.error("No permissions to access result field");
      }

      return null;
    }

    GerritCall(final WebSession session, final HttpServletRequest i,
        final HttpServletResponse o) {
      super(i, o);
      this.session = session;
      this.when = TimeUtil.nowMs();
    }

    @Override
    public MethodHandle getMethod() {
      if (currentMethod.get() == null) {
        return super.getMethod();
      }
      return currentMethod.get();
    }

    @Override
    public void onFailure(final Throwable error) {
      if (error instanceof IllegalArgumentException
          || error instanceof IllegalStateException) {
        super.onFailure(error);
      } else if (error instanceof OrmException
          || error instanceof RuntimeException) {
        onInternalFailure(error);
      } else {
        super.onFailure(error);
      }
    }

    @Override
    public boolean xsrfValidate() {
      final String keyIn = getXsrfKeyIn();
      if (keyIn == null || "".equals(keyIn)) {
        // Anonymous requests don't need XSRF protection, they shouldn't
        // be able to cause critical state changes.
        //
        return !session.isSignedIn();

      } else if (session.isSignedIn() && session.isValidXGerritAuth(keyIn)) {
        // The session must exist, and must be using this token.
        //
        session.getUser().setAccessPath(AccessPath.JSON_RPC);
        return true;
      }
      return false;
    }

    public WebSession getWebSession() {
      return session;
    }

    public long getWhen() {
      return when;
    }

    public long getElapsed() {
      return TimeUtil.nowMs() - when;
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy