Merge "Audit support for new RESTFul API."

This commit is contained in:
Shawn Pearce
2013-01-30 01:07:30 +00:00
committed by Gerrit Code Review
9 changed files with 323 additions and 131 deletions

View File

@@ -39,6 +39,8 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.net.HttpHeaders; 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.annotations.RequiresCapability;
import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate; import com.google.gerrit.extensions.restapi.AcceptsCreate;
@@ -140,14 +142,17 @@ public class RestApiServlet extends HttpServlet {
final Provider<CurrentUser> currentUser; final Provider<CurrentUser> currentUser;
final Provider<WebSession> webSession; final Provider<WebSession> webSession;
final Provider<ParameterParser> paramParser; final Provider<ParameterParser> paramParser;
final AuditService auditService;
@Inject @Inject
Globals(Provider<CurrentUser> currentUser, Globals(Provider<CurrentUser> currentUser,
Provider<WebSession> webSession, Provider<WebSession> webSession,
Provider<ParameterParser> paramParser) { Provider<ParameterParser> paramParser,
AuditService auditService) {
this.currentUser = currentUser; this.currentUser = currentUser;
this.webSession = webSession; this.webSession = webSession;
this.paramParser = paramParser; this.paramParser = paramParser;
this.auditService = auditService;
} }
} }
@@ -171,12 +176,16 @@ public class RestApiServlet extends HttpServlet {
@Override @Override
protected final void service(HttpServletRequest req, HttpServletResponse res) protected final void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException { throws ServletException, IOException {
long auditStartTs = System.currentTimeMillis();
CacheHeaders.setNotCacheable(res); CacheHeaders.setNotCacheable(res);
res.setHeader("Content-Disposition", "attachment"); res.setHeader("Content-Disposition", "attachment");
res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Content-Type-Options", "nosniff");
int status = SC_OK;
Object result = null;
Multimap<String, String> params = LinkedHashMultimap.create();
Object inputRequestBody = null;
try { try {
int status = SC_OK;
checkUserSession(req); checkUserSession(req);
List<IdString> path = splitPath(req); List<IdString> path = splitPath(req);
@@ -260,19 +269,18 @@ public class RestApiServlet extends HttpServlet {
} }
Multimap<String, String> config = LinkedHashMultimap.create(); Multimap<String, String> config = LinkedHashMultimap.create();
Multimap<String, String> params = LinkedHashMultimap.create();
ParameterParser.splitQueryString(req.getQueryString(), config, params); ParameterParser.splitQueryString(req.getQueryString(), config, params);
if (!globals.paramParser.get().parse(view, params, req, res)) { if (!globals.paramParser.get().parse(view, params, req, res)) {
return; return;
} }
Object result;
if (view instanceof RestModifyView<?, ?>) { if (view instanceof RestModifyView<?, ?>) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
RestModifyView<RestResource, Object> m = RestModifyView<RestResource, Object> m =
(RestModifyView<RestResource, Object>) view; (RestModifyView<RestResource, Object>) view;
result = m.apply(rsrc, parseRequest(req, inputType(m))); inputRequestBody = parseRequest(req, inputType(m));
result = m.apply(rsrc, inputRequestBody);
} else if (view instanceof RestReadView<?>) { } else if (view instanceof RestReadView<?>) {
result = ((RestReadView<RestResource>) view).apply(rsrc); result = ((RestReadView<RestResource>) view).apply(rsrc);
} else { } else {
@@ -298,24 +306,30 @@ public class RestApiServlet extends HttpServlet {
} }
} }
} catch (AuthException e) { } catch (AuthException e) {
replyError(res, SC_FORBIDDEN, e.getMessage()); replyError(res, status = SC_FORBIDDEN, e.getMessage());
} catch (BadRequestException e) { } catch (BadRequestException e) {
replyError(res, SC_BAD_REQUEST, e.getMessage()); replyError(res, status = SC_BAD_REQUEST, e.getMessage());
} catch (MethodNotAllowedException e) { } 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) { } catch (ResourceConflictException e) {
replyError(res, SC_CONFLICT, e.getMessage()); replyError(res, status = SC_CONFLICT, e.getMessage());
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
replyError(res, SC_PRECONDITION_FAILED, replyError(res, status = SC_PRECONDITION_FAILED,
Objects.firstNonNull(e.getMessage(), "Precondition failed")); Objects.firstNonNull(e.getMessage(), "Precondition failed"));
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
replyError(res, SC_NOT_FOUND, "Not found"); replyError(res, status = SC_NOT_FOUND, "Not found");
} catch (AmbiguousViewException e) { } catch (AmbiguousViewException e) {
replyError(res, SC_NOT_FOUND, e.getMessage()); replyError(res, status = SC_NOT_FOUND, e.getMessage());
} catch (JsonParseException e) { } 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) { } catch (Exception e) {
status = SC_INTERNAL_SERVER_ERROR;
handleException(e, req, res); 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));
} }
} }

View File

@@ -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;
}
}

View File

@@ -14,9 +14,10 @@
package com.google.gerrit.httpd.rpc; package com.google.gerrit.httpd.rpc;
import com.google.common.collect.Lists; import com.google.common.collect.ArrayListMultimap;
import com.google.gerrit.audit.AuditEvent; import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditService; import com.google.gerrit.audit.AuditService;
import com.google.gerrit.audit.RpcAuditEvent;
import com.google.gerrit.common.audit.Audit; import com.google.gerrit.common.audit.Audit;
import com.google.gerrit.common.auth.SignInRequired; import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.common.errors.NotSignedInException; import com.google.gerrit.common.errors.NotSignedInException;
@@ -37,11 +38,11 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.lang.reflect.Method;
import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
/** /**
* Base JSON servlet to ensure the current user is not forged. * Base JSON servlet to ensure the current user is not forged.
*/ */
@@ -68,7 +69,7 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
@Override @Override
protected GerritCall createActiveCall(final HttpServletRequest req, protected GerritCall createActiveCall(final HttpServletRequest req,
final HttpServletResponse rsp) { 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); currentCall.set(call);
return call; return call;
} }
@@ -134,62 +135,86 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
if (note != null) { if (note != null) {
final String sid = call.getWebSession().getSessionId(); final String sid = call.getWebSession().getSessionId();
final CurrentUser username = call.getWebSession().getCurrentUser(); final CurrentUser username = call.getWebSession().getCurrentUser();
final List<Object> args = final Multimap<String, ?> args =
extractParams(note, call); extractParams(note, call);
final String what = extractWhat(note, method.getName()); final String what = extractWhat(note, call);
final Object result = call.getResult(); final Object result = call.getResult();
audit.dispatch(new AuditEvent(sid, username, what, call.getWhen(), args, audit.dispatch(new RpcAuditEvent(sid, username, what, call.getWhen(),
result)); args, call.getHttpServletRequest().getMethod(), call.getHttpServletRequest().getMethod(),
((AuditedHttpServletResponse) (call.getHttpServletResponse()))
.getStatus(), result));
} }
} catch (Throwable all) { } catch (Throwable all) {
log.error("Unable to log the call", all); log.error("Unable to log the call", all);
} }
} }
private List<Object> extractParams(final Audit note, final GerritCall call) { private Multimap<String, ?> extractParams(final Audit note, final GerritCall call) {
List<Object> args = Lists.newArrayList(Arrays.asList(call.getParams())); Multimap<String, Object> 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()) { for (int idx : note.obfuscate()) {
args.set(idx, "*****"); args.removeAll("$" + idx);
args.put("$" + idx, "*****");
} }
return args; 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(); String what = note.action();
if (what.length() == 0) { if (what.length() == 0) {
boolean ccase = Character.isLowerCase(methodName.charAt(0)); what = call.getMethod().getName();
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();
} }
return what; return methodClass + "." + what;
} }
static class GerritCall extends ActiveCall { static class GerritCall extends ActiveCall {
private final WebSession session; private final WebSession session;
private final long when; private final long when;
private static final Field resultField; private static final Field resultField;
private static final Field methodField;
// Needed to allow access to non-public result field in GWT/JSON-RPC // Needed to allow access to non-public result field in GWT/JSON-RPC
static { static {
resultField = getPrivateField(ActiveCall.class, "result");
methodField = getPrivateField(MethodHandle.class, "method");
}
private static Field getPrivateField(Class<?> clazz, String fieldName) {
Field declaredField = null; Field declaredField = null;
try { try {
declaredField = ActiveCall.class.getDeclaredField("result"); declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true); declaredField.setAccessible(true);
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to expose RPS/JSON result field"); 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 // Surrogate of the missing getResult() in GWT/JSON-RPC

View File

@@ -14,22 +14,22 @@
package com.google.gerrit.audit; package com.google.gerrit.audit;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions; 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 com.google.gerrit.server.CurrentUser;
import java.util.Collections;
import java.util.List;
public class AuditEvent { public class AuditEvent {
public static final String UNKNOWN_SESSION_ID = "000000000000000000000000000"; public static final String UNKNOWN_SESSION_ID = "000000000000000000000000000";
private static final Object UNKNOWN_RESULT = "N/A"; protected static final Multimap<String, ?> EMPTY_PARAMS = HashMultimap.create();
public final String sessionId; public final String sessionId;
public final CurrentUser who; public final CurrentUser who;
public final long when; public final long when;
public final String what; public final String what;
public final List<?> params; public final Multimap<String, ?> params;
public final Object result; public final Object result;
public final long timeAtStart; public final long timeAtStart;
public final long elapsed; 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 * Creates a new audit event with results
* *
@@ -96,28 +83,20 @@ public class AuditEvent {
* @param result result of the event * @param result result of the event
*/ */
public AuditEvent(String sessionId, CurrentUser who, String what, long when, public AuditEvent(String sessionId, CurrentUser who, String what, long when,
List<?> params, Object result) { Multimap<String, ?> params, Object result) {
Preconditions.checkNotNull(what, "what is a mandatory not null param !"); 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.who = who;
this.what = what; this.what = what;
this.when = when; this.when = when;
this.timeAtStart = this.when; this.timeAtStart = this.when;
this.params = getValueWithDefault(params, Collections.emptyList()); this.params = Objects.firstNonNull(params, EMPTY_PARAMS);
this.uuid = new UUID(); this.uuid = new UUID();
this.result = result; this.result = result;
this.elapsed = System.currentTimeMillis() - timeAtStart; this.elapsed = System.currentTimeMillis() - timeAtStart;
} }
private <T> T getValueWithDefault(T value, T defaultValueIfNull) {
if (value == null) {
return defaultValueIfNull;
} else {
return value;
}
}
@Override @Override
public int hashCode() { public int hashCode() {
return uuid.hashCode(); return uuid.hashCode();
@@ -135,38 +114,7 @@ public class AuditEvent {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); return String.format("AuditEvent UUID:%s, SID:%s, TS:%d, who:%s, what:%s",
sb.append(uuid.toString()); uuid.get(), sessionId, when, who, what);
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();
} }
} }

View File

@@ -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<String, ?> 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;
}
}

View File

@@ -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<String, ?> params, String httpMethod, Object input,
int status, Object result) {
super(sessionId, who, what, when, params, httpMethod, input, status, result);
}
}

View File

@@ -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<String, ?> params, Object result) {
super(sessionId, who, what, when, params, result);
}
}

View File

@@ -196,7 +196,7 @@ class CommandFactoryProvider implements Provider<CommandFactory> {
private void log(final int rc) { private void log(final int rc) {
if (logged.compareAndSet(false, true)) { if (logged.compareAndSet(false, true)) {
log.onExecute(rc); log.onExecute(cmd, rc);
} }
} }

View File

@@ -14,9 +14,11 @@
package com.google.gerrit.sshd; package com.google.gerrit.sshd;
import com.google.gerrit.extensions.events.LifecycleListener; import com.google.common.collect.ArrayListMultimap;
import com.google.gerrit.audit.AuditEvent; import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditService; 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.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser; import com.google.gerrit.server.PeerDaemonUser;
@@ -42,7 +44,6 @@ import org.eclipse.jgit.util.QuotedString;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
@@ -101,7 +102,7 @@ class SshLog implements LifecycleListener {
void onLogin() { void onLogin() {
async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString())); 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) { void onAuthFail(final SshSession sd) {
@@ -127,18 +128,14 @@ class SshLog implements LifecycleListener {
} }
async.append(event); 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(); final Context ctx = context.get();
ctx.finished = System.currentTimeMillis(); ctx.finished = System.currentTimeMillis();
final String commandLine = ctx.getCommandLine(); String cmd = extractWhat(dcmd);
String cmd = QuotedString.BOURNE.quote(commandLine);
if (cmd == commandLine) {
cmd = "'" + commandLine + "'";
}
final LoggingEvent event = log(cmd); final LoggingEvent event = log(cmd);
event.setProperty(P_WAIT, (ctx.started - ctx.created) + "ms"); event.setProperty(P_WAIT, (ctx.started - ctx.created) + "ms");
@@ -165,19 +162,54 @@ class SshLog implements LifecycleListener {
event.setProperty(P_STATUS, status); event.setProperty(P_STATUS, status);
async.append(event); async.append(event);
audit(context.get(), status, getCommand(commandLine), audit(context.get(), status, dcmd);
CommandFactoryProvider.split(commandLine));
} }
private String getCommand(String commandLine) { private Multimap<String, ?> extractParameters(DispatchCommand dcmd) {
commandLine = commandLine.trim(); String[] cmdArgs = dcmd.getArguments();
int spacePos = commandLine.indexOf(' '); String paramName = null;
return (spacePos > 0 ? commandLine.substring(0, spacePos):commandLine); int argPos = 0;
Multimap<String, String> 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() { void onLogout() {
async.append(log("LOGOUT")); async.append(log("LOGOUT"));
audit(context.get(), "0", "LOGOUT", new String[] {}); audit(context.get(), "0", "LOGOUT");
} }
private LoggingEvent log(final String msg) { 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 String sid = extractSessionId(ctx);
final long created = extractCreated(ctx); final long created = extractCreated(ctx);
final String what = extractWhat(commandName, args); auditService.dispatch(new SshAuditEvent(sid, extractCurrentUser(ctx), cmd,
auditService.dispatch(new AuditEvent(sid, extractCurrentUser(ctx), "ssh:" created, null, result));
+ what, created, Arrays.asList(args), result));
} }
private String extractWhat(String commandName, String[] args) { void audit(Context ctx, Object result, DispatchCommand cmd) {
String result = commandName; final String sid = extractSessionId(ctx);
if ("gerrit".equals(commandName)) { final long created = extractCreated(ctx);
if (args.length > 1) auditService.dispatch(new SshAuditEvent(sid, extractCurrentUser(ctx),
result = "gerrit"+"."+args[1]; 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) { private long extractCreated(final Context ctx) {