diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java index acdfbaad41..32437fbf4c 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java @@ -39,6 +39,8 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.net.HttpHeaders; +import com.google.gerrit.audit.AuditService; +import com.google.gerrit.audit.HttpAuditEvent; import com.google.gerrit.extensions.annotations.RequiresCapability; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.restapi.AcceptsCreate; @@ -140,14 +142,17 @@ public class RestApiServlet extends HttpServlet { final Provider currentUser; final Provider webSession; final Provider paramParser; + final AuditService auditService; @Inject Globals(Provider currentUser, Provider webSession, - Provider paramParser) { + Provider paramParser, + AuditService auditService) { this.currentUser = currentUser; this.webSession = webSession; this.paramParser = paramParser; + this.auditService = auditService; } } @@ -171,12 +176,16 @@ public class RestApiServlet extends HttpServlet { @Override protected final void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + long auditStartTs = System.currentTimeMillis(); CacheHeaders.setNotCacheable(res); res.setHeader("Content-Disposition", "attachment"); res.setHeader("X-Content-Type-Options", "nosniff"); + int status = SC_OK; + Object result = null; + Multimap params = LinkedHashMultimap.create(); + Object inputRequestBody = null; try { - int status = SC_OK; checkUserSession(req); List path = splitPath(req); @@ -260,19 +269,18 @@ public class RestApiServlet extends HttpServlet { } Multimap config = LinkedHashMultimap.create(); - Multimap params = LinkedHashMultimap.create(); ParameterParser.splitQueryString(req.getQueryString(), config, params); if (!globals.paramParser.get().parse(view, params, req, res)) { return; } - Object result; if (view instanceof RestModifyView) { @SuppressWarnings("unchecked") RestModifyView m = (RestModifyView) view; - result = m.apply(rsrc, parseRequest(req, inputType(m))); + inputRequestBody = parseRequest(req, inputType(m)); + result = m.apply(rsrc, inputRequestBody); } else if (view instanceof RestReadView) { result = ((RestReadView) view).apply(rsrc); } else { @@ -298,24 +306,30 @@ public class RestApiServlet extends HttpServlet { } } } catch (AuthException e) { - replyError(res, SC_FORBIDDEN, e.getMessage()); + replyError(res, status = SC_FORBIDDEN, e.getMessage()); } catch (BadRequestException e) { - replyError(res, SC_BAD_REQUEST, e.getMessage()); + replyError(res, status = SC_BAD_REQUEST, e.getMessage()); } catch (MethodNotAllowedException e) { - replyError(res, SC_METHOD_NOT_ALLOWED, "Method not allowed"); + replyError(res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed"); } catch (ResourceConflictException e) { - replyError(res, SC_CONFLICT, e.getMessage()); + replyError(res, status = SC_CONFLICT, e.getMessage()); } catch (PreconditionFailedException e) { - replyError(res, SC_PRECONDITION_FAILED, + replyError(res, status = SC_PRECONDITION_FAILED, Objects.firstNonNull(e.getMessage(), "Precondition failed")); } catch (ResourceNotFoundException e) { - replyError(res, SC_NOT_FOUND, "Not found"); + replyError(res, status = SC_NOT_FOUND, "Not found"); } catch (AmbiguousViewException e) { - replyError(res, SC_NOT_FOUND, e.getMessage()); + replyError(res, status = SC_NOT_FOUND, e.getMessage()); } catch (JsonParseException e) { - replyError(res, SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request"); + replyError(res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request"); } catch (Exception e) { + status = SC_INTERNAL_SERVER_ERROR; handleException(e, req, res); + } finally { + globals.auditService.dispatch(new HttpAuditEvent(globals.webSession.get() + .getSessionId(), globals.currentUser.get(), req.getRequestURI(), + auditStartTs, params, req.getMethod(), inputRequestBody, status, + result)); } } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java new file mode 100644 index 0000000000..059b54c5bf --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/AuditedHttpServletResponse.java @@ -0,0 +1,64 @@ +// Copyright (C) 2013 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 java.io.IOException; + +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +class AuditedHttpServletResponse + extends HttpServletResponseWrapper + implements HttpServletResponse { + private int status; + + AuditedHttpServletResponse(HttpServletResponse response) { + super(response); + } + + int getStatus() { + return status; + } + + @Override + public void setStatus(int sc) { + super.setStatus(sc); + this.status = sc; + } + + @Override + public void setStatus(int sc, String sm) { + super.setStatus(sc, sm); + this.status = sc; + } + + @Override + public void sendError(int sc) throws IOException { + super.sendError(sc); + this.status = sc; + } + + @Override + public void sendError(int sc, String msg) throws IOException { + super.sendError(sc, msg); + this.status = sc; + } + + @Override + public void sendRedirect(String location) throws IOException { + super.sendRedirect(location); + this.status = SC_MOVED_TEMPORARILY; + } +} diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java index 35a4a440cf..94e853c937 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java @@ -14,9 +14,10 @@ package com.google.gerrit.httpd.rpc; -import com.google.common.collect.Lists; -import com.google.gerrit.audit.AuditEvent; +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.audit.Audit; import com.google.gerrit.common.auth.SignInRequired; import com.google.gerrit.common.errors.NotSignedInException; @@ -37,11 +38,11 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.List; +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. */ @@ -68,7 +69,7 @@ final class GerritJsonServlet extends JsonServlet @Override protected GerritCall createActiveCall(final HttpServletRequest req, final HttpServletResponse rsp) { - final GerritCall call = new GerritCall(session.get(), req, rsp); + final GerritCall call = new GerritCall(session.get(), req, new AuditedHttpServletResponse(rsp)); currentCall.set(call); return call; } @@ -134,62 +135,86 @@ final class GerritJsonServlet extends JsonServlet if (note != null) { final String sid = call.getWebSession().getSessionId(); final CurrentUser username = call.getWebSession().getCurrentUser(); - final List args = + final Multimap args = extractParams(note, call); - final String what = extractWhat(note, method.getName()); + final String what = extractWhat(note, call); final Object result = call.getResult(); - audit.dispatch(new AuditEvent(sid, username, what, call.getWhen(), args, - result)); + 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 List extractParams(final Audit note, final GerritCall call) { - List args = Lists.newArrayList(Arrays.asList(call.getParams())); + 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.set(idx, "*****"); + args.removeAll("$" + idx); + args.put("$" + idx, "*****"); } return args; } - private String extractWhat(final Audit note, final String methodName) { + private String extractWhat(final Audit note, final GerritCall call) { + String methodClass = call.getMethodClass().getName(); + methodClass = methodClass.substring(methodClass.lastIndexOf(".")+1); String what = note.action(); if (what.length() == 0) { - boolean ccase = Character.isLowerCase(methodName.charAt(0)); - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < methodName.length(); i++) { - char c = methodName.charAt(i); - if (ccase && !Character.isLowerCase(c)) { - sb.append(' '); - } - sb.append(Character.toLowerCase(c)); - } - what = sb.toString(); + what = call.getMethod().getName(); } - return what; + return methodClass + "." + 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 = ActiveCall.class.getDeclaredField("result"); + declaredField = clazz.getDeclaredField(fieldName); declaredField.setAccessible(true); } catch (Exception e) { log.error("Unable to expose RPS/JSON result field"); } + return declaredField; + } - resultField = 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 diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java index 364df807b6..173ced622e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java +++ b/gerrit-server/src/main/java/com/google/gerrit/audit/AuditEvent.java @@ -14,22 +14,22 @@ package com.google.gerrit.audit; +import com.google.common.base.Objects; import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.gerrit.server.CurrentUser; -import java.util.Collections; -import java.util.List; - public class AuditEvent { public static final String UNKNOWN_SESSION_ID = "000000000000000000000000000"; - private static final Object UNKNOWN_RESULT = "N/A"; + protected static final Multimap EMPTY_PARAMS = HashMultimap.create(); public final String sessionId; public final CurrentUser who; public final long when; public final String what; - public final List params; + public final Multimap params; public final Object result; public final long timeAtStart; public final long elapsed; @@ -72,19 +72,6 @@ public class AuditEvent { } } - /** - * Creates a new audit event. - * - * @param sessionId session id the event belongs to - * @param who principal that has generated the event - * @param what object of the event - * @param params parameters of the event - */ - public AuditEvent(String sessionId, CurrentUser who, String what, List params) { - this(sessionId, who, what, System.currentTimeMillis(), params, - UNKNOWN_RESULT); - } - /** * Creates a new audit event with results * @@ -96,28 +83,20 @@ public class AuditEvent { * @param result result of the event */ public AuditEvent(String sessionId, CurrentUser who, String what, long when, - List params, Object result) { + Multimap params, Object result) { Preconditions.checkNotNull(what, "what is a mandatory not null param !"); - this.sessionId = getValueWithDefault(sessionId, UNKNOWN_SESSION_ID); + this.sessionId = Objects.firstNonNull(sessionId, UNKNOWN_SESSION_ID); this.who = who; this.what = what; this.when = when; this.timeAtStart = this.when; - this.params = getValueWithDefault(params, Collections.emptyList()); + this.params = Objects.firstNonNull(params, EMPTY_PARAMS); this.uuid = new UUID(); this.result = result; this.elapsed = System.currentTimeMillis() - timeAtStart; } - private T getValueWithDefault(T value, T defaultValueIfNull) { - if (value == null) { - return defaultValueIfNull; - } else { - return value; - } - } - @Override public int hashCode() { return uuid.hashCode(); @@ -135,38 +114,7 @@ public class AuditEvent { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(uuid.toString()); - sb.append("|"); - sb.append(sessionId); - sb.append('|'); - sb.append(who); - sb.append('|'); - sb.append(when); - sb.append('|'); - sb.append(what); - sb.append('|'); - sb.append(elapsed); - sb.append('|'); - if (params != null) { - sb.append('['); - for (int i = 0; i < params.size(); i++) { - if (i > 0) sb.append(','); - - Object param = params.get(i); - if (param == null) { - sb.append("null"); - } else { - sb.append(param); - } - } - sb.append(']'); - } - sb.append('|'); - if (result != null) { - sb.append(result); - } - - return sb.toString(); + return String.format("AuditEvent UUID:%s, SID:%s, TS:%d, who:%s, what:%s", + uuid.get(), sessionId, when, who, what); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/HttpAuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/HttpAuditEvent.java new file mode 100644 index 0000000000..4bf9723102 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/audit/HttpAuditEvent.java @@ -0,0 +1,41 @@ +// Copyright (C) 2013 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.audit; + +import com.google.common.collect.Multimap; +import com.google.gerrit.server.CurrentUser; + +public class HttpAuditEvent extends AuditEvent { + public final String httpMethod; + public final int httpStatus; + public final Object input; + + /** + * Creates a new audit event with results + * + * @param sessionId session id the event belongs to + * @param who principal that has generated the event + * @param what object of the event + * @param when time-stamp of when the event started + * @param params parameters of the event + * @param result result of the event + */ + public HttpAuditEvent(String sessionId, CurrentUser who, String what, long when, + Multimap params, String httpMethod, Object input, int status, Object result) { + super(sessionId, who, what, when, params, result); + this.httpMethod = httpMethod; + this.input = input; + this.httpStatus = status; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/RpcAuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/RpcAuditEvent.java new file mode 100644 index 0000000000..c41ab3af87 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/audit/RpcAuditEvent.java @@ -0,0 +1,36 @@ +// Copyright (C) 2013 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.audit; + +import com.google.common.collect.Multimap; +import com.google.gerrit.server.CurrentUser; + +public class RpcAuditEvent extends HttpAuditEvent { + + /** + * Creates a new audit event with results + * + * @param sessionId session id the event belongs to + * @param who principal that has generated the event + * @param what object of the event + * @param when time-stamp of when the event started + * @param params parameters of the event + * @param result result of the event + */ + public RpcAuditEvent(String sessionId, CurrentUser who, String what, + long when, Multimap params, String httpMethod, Object input, + int status, Object result) { + super(sessionId, who, what, when, params, httpMethod, input, status, result); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/audit/SshAuditEvent.java b/gerrit-server/src/main/java/com/google/gerrit/audit/SshAuditEvent.java new file mode 100644 index 0000000000..58864c8fbc --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/audit/SshAuditEvent.java @@ -0,0 +1,25 @@ +// Copyright (C) 2013 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.audit; + +import com.google.common.collect.Multimap; +import com.google.gerrit.server.CurrentUser; + +public class SshAuditEvent extends AuditEvent { + + public SshAuditEvent(String sessionId, CurrentUser who, String what, + long when, Multimap params, Object result) { + super(sessionId, who, what, when, params, result); + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java index c40eb1d373..910f5f34e0 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandFactoryProvider.java @@ -196,7 +196,7 @@ class CommandFactoryProvider implements Provider { private void log(final int rc) { if (logged.compareAndSet(false, true)) { - log.onExecute(rc); + log.onExecute(cmd, rc); } } diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java index c9ac3e77a3..336490c9b2 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java @@ -14,9 +14,11 @@ package com.google.gerrit.sshd; -import com.google.gerrit.extensions.events.LifecycleListener; -import com.google.gerrit.audit.AuditEvent; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import com.google.gerrit.audit.AuditService; +import com.google.gerrit.audit.SshAuditEvent; +import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PeerDaemonUser; @@ -42,7 +44,6 @@ import org.eclipse.jgit.util.QuotedString; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -101,7 +102,7 @@ class SshLog implements LifecycleListener { void onLogin() { async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString())); - audit(context.get(), "0", "LOGIN", new String[] {}); + audit(context.get(), "0", "LOGIN"); } void onAuthFail(final SshSession sd) { @@ -127,18 +128,14 @@ class SshLog implements LifecycleListener { } async.append(event); - audit(null, "FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()}); + audit(null, "FAIL", "AUTH"); } - void onExecute(int exitValue) { + void onExecute(DispatchCommand dcmd, int exitValue) { final Context ctx = context.get(); ctx.finished = System.currentTimeMillis(); - final String commandLine = ctx.getCommandLine(); - String cmd = QuotedString.BOURNE.quote(commandLine); - if (cmd == commandLine) { - cmd = "'" + commandLine + "'"; - } + String cmd = extractWhat(dcmd); final LoggingEvent event = log(cmd); event.setProperty(P_WAIT, (ctx.started - ctx.created) + "ms"); @@ -165,19 +162,54 @@ class SshLog implements LifecycleListener { event.setProperty(P_STATUS, status); async.append(event); - audit(context.get(), status, getCommand(commandLine), - CommandFactoryProvider.split(commandLine)); + audit(context.get(), status, dcmd); } - private String getCommand(String commandLine) { - commandLine = commandLine.trim(); - int spacePos = commandLine.indexOf(' '); - return (spacePos > 0 ? commandLine.substring(0, spacePos):commandLine); + private Multimap extractParameters(DispatchCommand dcmd) { + String[] cmdArgs = dcmd.getArguments(); + String paramName = null; + int argPos = 0; + Multimap parms = ArrayListMultimap.create(); + for (int i = 2; i < cmdArgs.length; i++) { + String arg = cmdArgs[i]; + // -- stop parameters parsing + if (arg.equals("--")) { + for (i++; i < cmdArgs.length; i++) { + parms.put("$" + argPos++, cmdArgs[i]); + } + break; + } + // --param=value + int eqPos = arg.indexOf('='); + if (arg.startsWith("--") && eqPos > 0) { + parms.put(arg.substring(0, eqPos), arg.substring(eqPos + 1)); + continue; + } + // -p value or --param value + if (arg.startsWith("-")) { + if (paramName != null) { + parms.put(paramName, null); + } + paramName = arg; + continue; + } + // value + if (paramName == null) { + parms.put("$" + argPos++, arg); + } else { + parms.put(paramName, arg); + paramName = null; + } + } + if (paramName != null) { + parms.put(paramName, null); + } + return parms; } void onLogout() { async.append(log("LOGOUT")); - audit(context.get(), "0", "LOGOUT", new String[] {}); + audit(context.get(), "0", "LOGOUT"); } private LoggingEvent log(final String msg) { @@ -416,21 +448,28 @@ class SshLog implements LifecycleListener { } } - void audit(Context ctx, Object result, String commandName, String[] args) { + void audit(Context ctx, Object result, String cmd) { final String sid = extractSessionId(ctx); final long created = extractCreated(ctx); - final String what = extractWhat(commandName, args); - auditService.dispatch(new AuditEvent(sid, extractCurrentUser(ctx), "ssh:" - + what, created, Arrays.asList(args), result)); + auditService.dispatch(new SshAuditEvent(sid, extractCurrentUser(ctx), cmd, + created, null, result)); } - private String extractWhat(String commandName, String[] args) { - String result = commandName; - if ("gerrit".equals(commandName)) { - if (args.length > 1) - result = "gerrit"+"."+args[1]; + void audit(Context ctx, Object result, DispatchCommand cmd) { + final String sid = extractSessionId(ctx); + final long created = extractCreated(ctx); + auditService.dispatch(new SshAuditEvent(sid, extractCurrentUser(ctx), + extractWhat(cmd), created, extractParameters(cmd), result)); + } + + private String extractWhat(DispatchCommand dcmd) { + String commandName = dcmd.getCommandName(); + String[] args = dcmd.getArguments(); + if (args.length > 1) { + return commandName + "." + args[1]; + } else { + return commandName; } - return result; } private long extractCreated(final Context ctx) {