mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
ALF-4106 (ALF-4103): AuditService REST API; Query
- Added path- and value-based queries - Added limits - .desc.xml documentation - Further unit tests git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22187 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -38,6 +38,8 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
public static final String PARAM_APPLICATION = "application";
|
||||
public static final String PARAM_PATH="path";
|
||||
public static final String PARAM_ENABLED = "enabled";
|
||||
public static final String PARAM_VALUE = "value";
|
||||
public static final String PARAM_VALUE_TYPE = "valueType";
|
||||
public static final String PARAM_FROM_TIME = "fromTime";
|
||||
public static final String PARAM_TO_TIME = "toTime";
|
||||
public static final String PARAM_FROM_ID = "fromId";
|
||||
@@ -62,12 +64,13 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
public static final String JSON_KEY_PATH = "path";
|
||||
public static final String JSON_KEY_CLEARED = "cleared";
|
||||
|
||||
public static final String JSON_KEY_ENTRY_COUNT = "count";
|
||||
public static final String JSON_KEY_ENTRIES = "entries";
|
||||
public static final String JSON_QUERY_KEY_ID = "id";
|
||||
public static final String JSON_QUERY_KEY_APPLICATION = "application";
|
||||
public static final String JSON_QUERY_KEY_USER = "user";
|
||||
public static final String JSON_QUERY_KEY_TIME = "time";
|
||||
public static final String JSON_QUERY_KEY_VALUES = "values";
|
||||
public static final String JSON_KEY_ENTRY_ID = "id";
|
||||
public static final String JSON_KEY_ENTRY_APPLICATION = "application";
|
||||
public static final String JSON_KEY_ENTRY_USER = "user";
|
||||
public static final String JSON_KEY_ENTRY_TIME = "time";
|
||||
public static final String JSON_KEY_ENTRY_VALUES = "values";
|
||||
|
||||
/**
|
||||
* Logger that can be used by subclasses.
|
||||
@@ -139,6 +142,19 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
return Boolean.parseBoolean(enableStr);
|
||||
}
|
||||
|
||||
protected String getParamValue(WebScriptRequest req)
|
||||
{
|
||||
return req.getParameter(PARAM_VALUE);
|
||||
}
|
||||
|
||||
protected String getParamValueType(WebScriptRequest req)
|
||||
{
|
||||
return req.getParameter(PARAM_VALUE_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_FROM_TIME
|
||||
*/
|
||||
protected Long getParamFromTime(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_FROM_TIME);
|
||||
@@ -152,6 +168,9 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_TO_TIME
|
||||
*/
|
||||
protected Long getParamToTime(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_TO_TIME);
|
||||
@@ -165,6 +184,9 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_FROM_ID
|
||||
*/
|
||||
protected Long getParamFromId(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_FROM_ID);
|
||||
@@ -178,6 +200,9 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_TO_ID
|
||||
*/
|
||||
protected Long getParamToId(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_TO_ID);
|
||||
@@ -191,11 +216,17 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_USER
|
||||
*/
|
||||
protected String getParamUser(WebScriptRequest req)
|
||||
{
|
||||
return req.getParameter(PARAM_USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_FORWARD
|
||||
*/
|
||||
protected boolean getParamForward(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_FORWARD);
|
||||
@@ -206,6 +237,9 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
return Boolean.parseBoolean(paramStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_LIMIT
|
||||
*/
|
||||
protected int getParamLimit(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_LIMIT);
|
||||
@@ -219,6 +253,9 @@ public abstract class AbstractAuditWebScript extends DeclarativeWebScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #DEFAULT_VERBOSE
|
||||
*/
|
||||
protected boolean getParamVerbose(WebScriptRequest req)
|
||||
{
|
||||
String paramStr = req.getParameter(PARAM_VERBOSE);
|
||||
|
@@ -20,6 +20,7 @@ package org.alfresco.repo.web.scripts.audit;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -27,6 +28,7 @@ import java.util.Map;
|
||||
import org.alfresco.service.cmr.audit.AuditQueryParameters;
|
||||
import org.alfresco.service.cmr.audit.AuditService.AuditApplication;
|
||||
import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback;
|
||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||
import org.springframework.extensions.webscripts.Cache;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
@@ -44,6 +46,10 @@ public class AuditQueryGet extends AbstractAuditWebScript
|
||||
final Map<String, Object> model = new HashMap<String, Object>(7);
|
||||
|
||||
String appName = getParamAppName(req);
|
||||
String path = getParamPath(req);
|
||||
|
||||
Serializable value = getParamValue(req);
|
||||
String valueType = getParamValueType(req);
|
||||
Long fromTime = getParamFromTime(req);
|
||||
Long toTime = getParamToTime(req);
|
||||
Long fromId = getParamFromId(req);
|
||||
@@ -63,6 +69,25 @@ public class AuditQueryGet extends AbstractAuditWebScript
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the value to the correct type
|
||||
if (value != null && valueType != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Serializable> clazz = (Class<? extends Serializable>) Class.forName(valueType);
|
||||
value = DefaultTypeConverter.INSTANCE.convert(clazz, value);
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.value.classNotFound", valueType);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.value.convertFailed", value, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the query
|
||||
AuditQueryParameters params = new AuditQueryParameters();
|
||||
params.setApplicationName(appName);
|
||||
@@ -72,6 +97,10 @@ public class AuditQueryGet extends AbstractAuditWebScript
|
||||
params.setToId(toId);
|
||||
params.setUser(user);
|
||||
params.setForward(forward);
|
||||
if (path != null || value != null)
|
||||
{
|
||||
params.addSearchKey(path, value);
|
||||
}
|
||||
|
||||
final List<Map<String, Object>> entries = new ArrayList<Map<String,Object>>(limit);
|
||||
AuditQueryCallback callback = new AuditQueryCallback()
|
||||
@@ -97,16 +126,16 @@ public class AuditQueryGet extends AbstractAuditWebScript
|
||||
Map<String, Serializable> values)
|
||||
{
|
||||
Map<String, Object> entry = new HashMap<String, Object>(11);
|
||||
entry.put(JSON_QUERY_KEY_ID, entryId);
|
||||
entry.put(JSON_QUERY_KEY_APPLICATION, applicationName);
|
||||
entry.put(JSON_KEY_ENTRY_ID, entryId);
|
||||
entry.put(JSON_KEY_ENTRY_APPLICATION, applicationName);
|
||||
if (user != null)
|
||||
{
|
||||
entry.put(JSON_QUERY_KEY_USER, user);
|
||||
entry.put(JSON_KEY_ENTRY_USER, user);
|
||||
}
|
||||
entry.put(JSON_QUERY_KEY_TIME, new Long(time));
|
||||
entry.put(JSON_KEY_ENTRY_TIME, new Date(time));
|
||||
if (values != null)
|
||||
{
|
||||
entry.put(JSON_QUERY_KEY_VALUES, values);
|
||||
entry.put(JSON_KEY_ENTRY_VALUES, values);
|
||||
}
|
||||
entries.add(entry);
|
||||
|
||||
@@ -116,6 +145,7 @@ public class AuditQueryGet extends AbstractAuditWebScript
|
||||
|
||||
auditService.auditQuery(callback, params, limit);
|
||||
|
||||
model.put(JSON_KEY_ENTRY_COUNT, entries.size());
|
||||
model.put(JSON_KEY_ENTRIES, entries);
|
||||
|
||||
// Done
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.alfresco.repo.web.scripts.audit;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
@@ -31,6 +32,7 @@ import org.alfresco.service.cmr.security.AuthenticationService;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.extensions.surf.util.ISO8601DateFormat;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer;
|
||||
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
|
||||
@@ -212,7 +214,7 @@ public class AuditWebScriptTest extends BaseWebScriptTest
|
||||
/**
|
||||
* Perform a failed login attempt
|
||||
*/
|
||||
private void loginWithFailure() throws Exception
|
||||
private void loginWithFailure(final String username) throws Exception
|
||||
{
|
||||
// Force a failed login
|
||||
RunAsWork<Void> failureWork = new RunAsWork<Void>()
|
||||
@@ -222,7 +224,7 @@ public class AuditWebScriptTest extends BaseWebScriptTest
|
||||
{
|
||||
try
|
||||
{
|
||||
authenticationService.authenticate("domino", "crud".toCharArray());
|
||||
authenticationService.authenticate(username, "crud".toCharArray());
|
||||
fail("Failed to force authentication failure");
|
||||
}
|
||||
catch (AuthenticationException e)
|
||||
@@ -240,7 +242,7 @@ public class AuditWebScriptTest extends BaseWebScriptTest
|
||||
long now = System.currentTimeMillis();
|
||||
long future = Long.MAX_VALUE;
|
||||
|
||||
loginWithFailure();
|
||||
loginWithFailure(getName());
|
||||
|
||||
// Delete audit entries that could not have happened
|
||||
String url = "/api/audit/clear/" + APP_REPO_NAME + "?fromTime=" + future;
|
||||
@@ -249,15 +251,24 @@ public class AuditWebScriptTest extends BaseWebScriptTest
|
||||
JSONObject json = new JSONObject(response.getContentAsString());
|
||||
int cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
|
||||
assertEquals("Could not have cleared more than 0", 0, cleared);
|
||||
|
||||
|
||||
// Delete the entry (at least)
|
||||
url = "/api/audit/clear/" + APP_REPO_NAME + "?fromTime=" + now + "&toTime=" + future;
|
||||
req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
|
||||
assertTrue("Should have cleared at least 1 entry", cleared > 0);
|
||||
|
||||
// Delete all entries
|
||||
url = "/api/audit/clear/" + APP_REPO_NAME;;
|
||||
req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
cleared = json.getInt(AbstractAuditWebScript.JSON_KEY_CLEARED);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void testQueryAuditRepo() throws Exception
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -266,14 +277,98 @@ public class AuditWebScriptTest extends BaseWebScriptTest
|
||||
auditService.setAuditEnabled(true);
|
||||
auditService.enableAudit(APP_REPO_NAME, APP_REPO_PATH);
|
||||
|
||||
loginWithFailure();
|
||||
loginWithFailure(getName());
|
||||
|
||||
// Delete audit entries that could not have happened
|
||||
// Query for audit entries that could not have happened
|
||||
String url = "/api/audit/query/" + APP_REPO_NAME + "?fromTime=" + now + "&verbose=true";
|
||||
TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url);
|
||||
Response response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
JSONObject json = new JSONObject(response.getContentAsString());
|
||||
Long entryCount = json.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_COUNT);
|
||||
JSONArray jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertTrue("Expected at least one entry", jsonEntries.length() > 0);
|
||||
assertEquals("Entry count and physical count don't match", new Long(jsonEntries.length()), entryCount);
|
||||
JSONObject jsonEntry = jsonEntries.getJSONObject(0);
|
||||
Long entryId = jsonEntry.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_ID);
|
||||
assertNotNull("No entry ID", entryId);
|
||||
String entryTimeStr = jsonEntry.getString(AbstractAuditWebScript.JSON_KEY_ENTRY_TIME);
|
||||
assertNotNull("No entry time String", entryTimeStr);
|
||||
Date entryTime = ISO8601DateFormat.parse((String)entryTimeStr); // Check conversion
|
||||
JSONObject jsonValues = jsonEntry.getJSONObject(AbstractAuditWebScript.JSON_KEY_ENTRY_VALUES);
|
||||
String entryUsername = jsonValues.getString("/repository/login/error/user");
|
||||
assertEquals("Didn't find the login-failure-user", getName(), entryUsername);
|
||||
|
||||
// Query using well-known ID
|
||||
Long fromEntryId = entryId; // Search is inclusive on the 'from' side
|
||||
Long toEntryId = entryId.longValue() + 1L; // Search is exclusive on the 'to' side
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "?fromId=" + fromEntryId + "&toId=" + toEntryId;
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertEquals("Incorrect number of search results", 1, jsonEntries.length());
|
||||
|
||||
// Query using a non-existent entry path
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "/repository/login/error/userXXX" + "?verbose=true";
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertTrue("Should not have found anything", jsonEntries.length() == 0);
|
||||
|
||||
// Query using a good entry path
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "/repository/login/error/user" + "?verbose=true";
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertTrue("Should have found entries", jsonEntries.length() > 0);
|
||||
|
||||
// Now login with failure using a GUID and ensure that we can find it
|
||||
String missingUser = new Long(System.currentTimeMillis()).toString();
|
||||
|
||||
// Query for event that has not happened
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "/repository/login/error/user" + "?value=" + missingUser;
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertEquals("Incorrect number of search results", 0, jsonEntries.length());
|
||||
|
||||
loginWithFailure(missingUser);
|
||||
|
||||
// Query for event that has happened once
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "/repository/login/error/user" + "?value=" + missingUser;
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertEquals("Incorrect number of search results", 1, jsonEntries.length());
|
||||
|
||||
// Query for event, but casting the value to the incorrect type
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "/repository/login/error/user" + "?value=" + missingUser + "&valueType=java.lang.Long";
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertEquals("Incorrect number of search results", 0, jsonEntries.length());
|
||||
|
||||
// Test what happens when the target data needs encoding
|
||||
String oddUser = "%$£\\\"\'";
|
||||
loginWithFailure(oddUser);
|
||||
|
||||
// Query for the event limiting to one by count and descending (i.e. get last)
|
||||
url = "/api/audit/query/" + APP_REPO_NAME + "?forward=false&limit=1&verbose=true";
|
||||
req = new TestWebScriptServer.GetRequest(url);
|
||||
response = sendRequest(req, Status.STATUS_OK, admin);
|
||||
json = new JSONObject(response.getContentAsString());
|
||||
jsonEntries = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_ENTRIES);
|
||||
assertEquals("Incorrect number of search results", 1, jsonEntries.length());
|
||||
jsonEntry = jsonEntries.getJSONObject(0);
|
||||
entryId = jsonEntry.getLong(AbstractAuditWebScript.JSON_KEY_ENTRY_ID);
|
||||
assertNotNull("No entry ID", entryId);
|
||||
jsonValues = jsonEntry.getJSONObject(AbstractAuditWebScript.JSON_KEY_ENTRY_VALUES);
|
||||
entryUsername = jsonValues.getString("/repository/login/error/user");
|
||||
assertEquals("Didn't find the login-failure-user", oddUser, entryUsername);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user