diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 837429f399d..e9f54bc9917 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -152,6 +152,8 @@ Improvements * SOLR-13961: When using partial/atomic updates to remove child documents, we now support setting to null or an empty list as equivalent to the "remove" command. (Thomas Wöckinger) +* SOLR-13969: Clean up and document AuditEvent API (janhoy) + Optimizations --------------------- (No changes) diff --git a/solr/core/src/java/org/apache/solr/security/AuditEvent.java b/solr/core/src/java/org/apache/solr/security/AuditEvent.java index 17db3407569..adcfa5b7bdc 100644 --- a/solr/core/src/java/org/apache/solr/security/AuditEvent.java +++ b/solr/core/src/java/org/apache/solr/security/AuditEvent.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.servlet.ServletUtils; @@ -48,7 +49,7 @@ import static org.apache.solr.security.AuditEvent.EventType.ERROR; */ public class AuditEvent { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private StringBuffer requestUrl; + private String baseUrl; private String nodeName; private String message; private Level level; @@ -69,10 +70,10 @@ public class AuditEvent { private EventType eventType; private AuthorizationResponse autResponse; private RequestType requestType; - private double QTime = -1; + private double qTime = -1; private int status = -1; private Throwable exception; - + /* Predefined event types. Custom types can be made through constructor */ public enum EventType { AUTHENTICATED("Authenticated", "User successfully authenticated", Level.INFO, -1), @@ -134,7 +135,7 @@ public class AuditEvent { this.httpMethod = httpRequest.getMethod(); this.httpQueryString = httpRequest.getQueryString(); this.headers = getHeadersFromRequest(httpRequest); - this.requestUrl = httpRequest.getRequestURL(); + this.baseUrl = httpRequest.getRequestURL().toString(); this.nodeName = MDC.get(ZkStateReader.NODE_NAME_PROP); SolrRequestParsers.parseQueryString(httpQueryString).forEach(sp -> { this.solrParams.put(sp.getKey(), Arrays.asList(sp.getValue())); @@ -222,75 +223,135 @@ public class AuditEvent { } } } - + + /** + * The human readable message about this event + */ public String getMessage() { return message; } + /** + * Level of this event. Can be INFO, WARN or ERROR + * @return {@link Level} enum + */ public Level getLevel() { return level; } + /** + * Date that the event happened + */ public Date getDate() { return date; } + /** + * Username of logged in user, or null if no authenticated user + */ public String getUsername() { return username; } + /** + * Session identifier + */ public String getSession() { return session; } + /** + * IP address of the client doing the request + */ public String getClientIp() { return clientIp; } - + + /** + * A general purpose context map with potential extra information about the event + */ public Map getContext() { return context; } - + + /** + * List of collection names involved in request + */ public List getCollections() { return collections; } + /** + * Identifies the resource being operated on. This is not the same as URL path. + * For queries the resource is relative to collection name, e.g. /select or /update. + * For other events the resource may be /api/node/health or /admin/collection + */ public String getResource() { return resource; } + /** + * The HTTP method. E.g. GET, POST, PUT + */ public String getHttpMethod() { return httpMethod; } + /** + * Query part of URL or null if query part + */ public String getHttpQueryString() { return httpQueryString; } + /** + * EventType tells the outcome of the event such as REJECTED, UNAUTHORIZED or ERROR + * @return {@link EventType} enum + */ public EventType getEventType() { return eventType; } + /** + * Host name of the Solr node logging the event + */ public String getSolrHost() { return solrHost; } + /** + * IP address of the Solr node logging the event + */ public String getSolrIp() { return solrIp; } + /** + * Port number of the Solr node logging the event + */ public int getSolrPort() { return solrPort; } + /** + * Map of all HTTP request headers belonging to the request + */ public Map getHeaders() { return headers; } + /** + * Map of all Solr request parameters attached to the request. Pulled from url + */ public Map> getSolrParams() { return solrParams; } + /** + * Gets first value of a certain Solr request parameter + * @param key name of request parameter to retrieve + * @return String value of the first value, regardless of number of valies + */ public String getSolrParamAsString(String key) { List v = getSolrParams().get(key); if (v != null && v.size() > 0) { @@ -298,42 +359,84 @@ public class AuditEvent { } return null; } - + + /** + * The authorization response object from authorization plugin, or null authz has not happened + */ public AuthorizationResponse getAutResponse() { return autResponse; } + /** + * Node name of Solr node, on the internal format host:port_context, e.g. 10.0.0.1:8983_solr + */ public String getNodeName() { return nodeName; } + /** + * Determines the type of request. Can be ADMIN, SEARCH, UPDATE, STREAMING, UNKNOWN + * @return {@link RequestType} enum + */ public RequestType getRequestType() { return requestType; } + /** + * HTTP status code of event, i.e. 200 = OK, 401 = unauthorized + */ public int getStatus() { return status; } + /** + * Request time in milliseconds for completed requests + */ public double getQTime() { - return QTime; + return qTime; } - + + /** + * In case of ERROR event, find the exception causing the error + */ public Throwable getException() { return exception; } + /** + * Get baseUrl as StringBuffer for back compat with previous version + * @deprecated Please use {@link #getBaseUrl()} instead + * @return StringBuffer of the base url without query part + */ + @Deprecated + @JsonIgnore public StringBuffer getRequestUrl() { - return requestUrl; + return new StringBuffer(baseUrl); + } + + /** + * Full URL of the original request. This is {@link #baseUrl} + "?" + {@link #httpQueryString}. + * Returns null if not set + */ + public String getUrl() { + if (baseUrl == null) return null; + return baseUrl + (httpQueryString != null ? "?" + httpQueryString : ""); + } + + /** + * First part of URL of the request, but not including request parameters, or null if not set + */ + public String getBaseUrl() { + return baseUrl; } // Setters, builder style - public AuditEvent setRequestUrl(StringBuffer requestUrl) { - this.requestUrl = requestUrl; + public AuditEvent setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; return this; } - + public AuditEvent setSession(String session) { this.session = session; return this; @@ -429,8 +532,8 @@ public class AuditEvent { return this; } - public AuditEvent setQTime(double QTime) { - this.QTime = QTime; + public AuditEvent setQTime(double qTime) { + this.qTime = qTime; return this; } diff --git a/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java b/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java index 46064828c8a..62eb27d06a9 100644 --- a/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java +++ b/solr/core/src/test/org/apache/solr/security/AuditLoggerPluginTest.java @@ -38,6 +38,13 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 { .setMessage("Anonymous") .setResource("/collection1") .setDate(SAMPLE_DATE); + protected static final AuditEvent EVENT_WITH_URL = new AuditEvent(AuditEvent.EventType.ANONYMOUS) + .setHttpMethod("GET") + .setMessage("Anonymous") + .setResource("/collection1") + .setBaseUrl("http://myserver/mypath") + .setHttpQueryString("a=b&c=d") + .setDate(SAMPLE_DATE); protected static final AuditEvent EVENT_ANONYMOUS_REJECTED = new AuditEvent(AuditEvent.EventType.ANONYMOUS_REJECTED) .setHttpMethod("GET") .setMessage("Anonymous rejected") @@ -194,6 +201,18 @@ public class AuditLoggerPluginTest extends SolrTestCaseJ4 { plugin.formatter.formatEvent(EVENT_ANONYMOUS)); assertEquals("{\"message\":\"Authenticated\",\"level\":\"INFO\",\"date\":" + SAMPLE_DATE.getTime() + ",\"username\":\"Jan\",\"solrParams\":{},\"solrPort\":0,\"resource\":\"/collection1\",\"httpMethod\":\"GET\",\"eventType\":\"AUTHENTICATED\",\"status\":-1,\"qtime\":-1.0}", plugin.formatter.formatEvent(EVENT_AUTHENTICATED)); - } - + } + + @Test + public void getBaseUrl() { + assertEquals("http://myserver/mypath", EVENT_WITH_URL.getBaseUrl()); + // Deprecated + assertEquals("http://myserver/mypath", EVENT_WITH_URL.getRequestUrl().toString()); + } + + @Test + public void getUrl() { + assertEquals("http://myserver/mypath?a=b&c=d", + EVENT_WITH_URL.getUrl()); + } }