Merge "Audit support for new RESTFul API."
This commit is contained in:
@@ -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> currentUser;
|
||||
final Provider<WebSession> webSession;
|
||||
final Provider<ParameterParser> paramParser;
|
||||
final AuditService auditService;
|
||||
|
||||
@Inject
|
||||
Globals(Provider<CurrentUser> currentUser,
|
||||
Provider<WebSession> webSession,
|
||||
Provider<ParameterParser> paramParser) {
|
||||
Provider<ParameterParser> 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<String, String> params = LinkedHashMultimap.create();
|
||||
Object inputRequestBody = null;
|
||||
|
||||
try {
|
||||
int status = SC_OK;
|
||||
checkUserSession(req);
|
||||
|
||||
List<IdString> path = splitPath(req);
|
||||
@@ -260,19 +269,18 @@ public class RestApiServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
Multimap<String, String> config = LinkedHashMultimap.create();
|
||||
Multimap<String, String> 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<RestResource, Object> m =
|
||||
(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<?>) {
|
||||
result = ((RestReadView<RestResource>) 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<GerritJsonServlet.GerritCall>
|
||||
@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<GerritJsonServlet.GerritCall>
|
||||
if (note != null) {
|
||||
final String sid = call.getWebSession().getSessionId();
|
||||
final CurrentUser username = call.getWebSession().getCurrentUser();
|
||||
final List<Object> args =
|
||||
final Multimap<String, ?> 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<Object> extractParams(final Audit note, final GerritCall call) {
|
||||
List<Object> args = Lists.newArrayList(Arrays.asList(call.getParams()));
|
||||
private Multimap<String, ?> extractParams(final Audit note, final GerritCall call) {
|
||||
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()) {
|
||||
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
|
||||
|
||||
@@ -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<String, ?> 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<String, ?> 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<String, ?> 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> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class CommandFactoryProvider implements Provider<CommandFactory> {
|
||||
|
||||
private void log(final int rc) {
|
||||
if (logged.compareAndSet(false, true)) {
|
||||
log.onExecute(rc);
|
||||
log.onExecute(cmd, rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, ?> extractParameters(DispatchCommand dcmd) {
|
||||
String[] cmdArgs = dcmd.getArguments();
|
||||
String paramName = null;
|
||||
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() {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user