From a1f7724db4027383966038554bf70e20fcdca550 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Sat, 14 Aug 2010 20:42:26 +0000 Subject: [PATCH] ALF-4106 (ALF-4103): AuditService REST API - Full start/stop/status using WebScripts - AuditService API additions to support - .ftl to generate json response - Some javadoc and debug additions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21802 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repository/audit/application.lib.ftl | 8 ++ .../repository/audit/control.get.desc.xml | 5 +- .../repository/audit/control.get.json.ftl | 13 ++ .../repository/audit/control.post.desc.xml | 13 +- .../repository/audit/control.post.json.ftl | 3 + .../repository/audit/control.properties | 4 +- .../scripts/audit/AbstractAuditWebScript.java | 62 +++------ .../web/scripts/audit/AuditControlGet.java | 76 ++++------- .../web/scripts/audit/AuditControlPost.java | 35 +++-- .../web/scripts/audit/AuditWebScriptTest.java | 128 ++++++++++++++---- 10 files changed, 213 insertions(+), 134 deletions(-) create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/audit/application.lib.ftl create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.json.ftl create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.json.ftl diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/application.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/application.lib.ftl new file mode 100644 index 0000000000..97f15acfc0 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/application.lib.ftl @@ -0,0 +1,8 @@ +<#-- Renders an Audit Application. --> +<#macro auditApplicationJSON auditApplication> + { + "name": "${auditApplication.name}", + "path" : "${auditApplication.key}", + "enabled" : ${auditApplication.enabled?string("true","false")} + } + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.desc.xml index a80feccc7c..a0fe510599 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.desc.xml @@ -1,7 +1,8 @@ Alfresco Audit Service Control Get audit status for a given application and path - /api/audit/control?application={application?}&path={path?} + /api/audit/control + /api/audit/control/{application}/{path} admin required @@ -13,7 +14,7 @@ path - Path within the application (omit to assume application root) + Path within the application (the root key of the application at least) diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.json.ftl new file mode 100644 index 0000000000..0ae8a76a82 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.get.json.ftl @@ -0,0 +1,13 @@ +<#import "application.lib.ftl" as auditApplicationLib /> +<#escape x as jsonUtils.encodeJSONString(x)> +{ + "enabled" : ${enabled?string("true","false")}, + "applications": + [ + <#list applications as application> + <@auditApplicationLib.auditApplicationJSON auditApplication=application /> + <#if application_has_next>, + + ] +} + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.desc.xml index c359e65d54..01b154addb 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.desc.xml @@ -1,23 +1,24 @@ Alfresco Audit Service Control Change the audit status for a given application and path - /api/audit/control/{action}?application={application?}&path={path?} + /api/audit/control?enable={enable} + /api/audit/control/{application}/{path}?enable={enable} admin required internal - - action - Set to 'enable' or 'disable' to change the audit state - application Name of the audit application (omit to assume all applications) path - Path within the application (omit to assume application root) + Path within the application (the root key of the application at least) + + + enable + 'true' or 'false' to change the audit state diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.json.ftl new file mode 100644 index 0000000000..f5fd0a83ac --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.post.json.ftl @@ -0,0 +1,3 @@ +{ + "enabled" : ${enabled?string("true","false")} +} diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.properties b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.properties index c364f6c653..2d6356d41b 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.properties +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/audit/control.properties @@ -1,4 +1,4 @@ # Audit Control Web Script I18N -audit.err.app.mandatory=Parameter 'app' is mandatory -audit.err.path.startsWith=Parameter 'path', when supplied, must start with '/' +audit.err.app.notFound=Application not found: {0} +audit.err.path.notProvided=No path was supplied after the application name. audit.err.action.invalid=Parameter 'action' must be either 'enable' or 'disable' \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/audit/AbstractAuditWebScript.java b/source/java/org/alfresco/repo/web/scripts/audit/AbstractAuditWebScript.java index b6e9127aa6..926e4bdb3a 100644 --- a/source/java/org/alfresco/repo/web/scripts/audit/AbstractAuditWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/audit/AbstractAuditWebScript.java @@ -24,9 +24,7 @@ import org.alfresco.service.cmr.audit.AuditService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.extensions.webscripts.AbstractWebScript; -import org.springframework.extensions.webscripts.Status; -import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.DeclarativeWebScript; import org.springframework.extensions.webscripts.WebScriptRequest; /** @@ -35,18 +33,16 @@ import org.springframework.extensions.webscripts.WebScriptRequest; * @author Derek Hulley * @since 3.4 */ -public abstract class AbstractAuditWebScript extends AbstractWebScript +public abstract class AbstractAuditWebScript extends DeclarativeWebScript { - public static final String PARAM_APP = "app"; + public static final String PARAM_APPLICATION = "application"; public static final String PARAM_PATH="path"; - public static final String PARAM_ACTION = "action"; + public static final String PARAM_ENABLED = "enabled"; + public static final String JSON_KEY_ENABLED = "enabled"; public static final String JSON_KEY_APPLICATIONS = "applications"; public static final String JSON_KEY_NAME = "name"; public static final String JSON_KEY_PATH = "path"; - public static final String JSON_KEY_ENABLED = "enabled"; - - private static enum AuditWebScriptAction {enable, disable}; /** * Logger that can be used by subclasses. @@ -76,59 +72,45 @@ public abstract class AbstractAuditWebScript extends AbstractWebScript /** * Get the application name from the request. * - * @param mandatory true if the application name is expected * @return Returns the application name or null if not present */ - protected final String getApp(WebScriptRequest req, boolean mandatory) + protected final String getAppName(WebScriptRequest req) { - // All URLs must contain the application - String paramApp = req.getParameter(PARAM_APP); - if (paramApp == null && mandatory) + Map templateVars = req.getServiceMatch().getTemplateVars(); + String app = templateVars.get(PARAM_APPLICATION); + if (app == null || app.length() == 0) { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.app.mandatory"); + return null; + } + else + { + return app; } - return paramApp; } /** - * Get the path from the request. If it is mandatory, then a value must have been supplied - * otherwise null is returned. - * @param mandatory true if the parameter is expected + * Get the path from the request. + * * @return Returns the path or null if not present */ protected String getPath(WebScriptRequest req) { - String paramPath = req.getParameter(PARAM_PATH); + Map templateVars = req.getServiceMatch().getTemplateVars(); + String paramPath = templateVars.get(PARAM_PATH); if (paramPath == null || paramPath.length() == 0) { paramPath = null; } else if (!paramPath.startsWith("/")) { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.path.startsWith"); + // It won't ever, so we can expect to be here all the time + paramPath = "/" + paramPath; } return paramPath; } protected boolean getEnableDisable(WebScriptRequest req) { - Map templateVars = req.getServiceMatch().getTemplateVars(); - String enableStr = templateVars.get(PARAM_ACTION); - try - { - AuditWebScriptAction action = AuditWebScriptAction.valueOf(enableStr); - switch (action) - { - case enable: - return true; - case disable: - return false; - default: - return false; - } - } - catch (IllegalArgumentException e) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.action.invalid"); - } + String enableStr = req.getParameter(PARAM_ENABLED); + return Boolean.parseBoolean(enableStr); } } diff --git a/source/java/org/alfresco/repo/web/scripts/audit/AuditControlGet.java b/source/java/org/alfresco/repo/web/scripts/audit/AuditControlGet.java index 6d2c6e21da..c2cf5adf6c 100644 --- a/source/java/org/alfresco/repo/web/scripts/audit/AuditControlGet.java +++ b/source/java/org/alfresco/repo/web/scripts/audit/AuditControlGet.java @@ -18,15 +18,15 @@ */ package org.alfresco.repo.web.scripts.audit; -import java.io.IOException; -import java.nio.charset.Charset; import java.util.Collections; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; +import org.alfresco.service.cmr.audit.AuditService.AuditApplication; +import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; -import org.springframework.extensions.webscripts.WebScriptResponse; -import org.springframework.extensions.webscripts.json.JSONWriter; /** * @author Derek Hulley @@ -35,60 +35,40 @@ import org.springframework.extensions.webscripts.json.JSONWriter; public class AuditControlGet extends AbstractAuditWebScript { @Override - public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) { - // return the unique transfer id (the lock id) - JSONWriter json = new JSONWriter(res.getWriter()); - - String app = getApp(req, false); + Map model = new HashMap(7); + + String appName = getAppName(req); String path = getPath(req); - Set apps = auditService.getAuditApplications(); + boolean enabledGlobal = auditService.isAuditEnabled(); + Map appsByName = auditService.getAuditApplications(); // Check that the application exists - if (app != null) + if (appName != null) { - if (apps.contains(app)) + if (path == null) { - apps = Collections.singleton(app); + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "audit.err.path.notProvided"); } - else + + AuditApplication app = appsByName.get(appName); + if (app == null) { - apps = Collections.emptySet(); + throw new WebScriptException(Status.STATUS_NOT_FOUND, "audit.err.app.notFound", app); } + // Discard all the other applications + appsByName = Collections.singletonMap(appName, app); } - boolean enabledGlobal = auditService.isAuditEnabled(); - json.startObject(); - { - json.writeValue(JSON_KEY_ENABLED, enabledGlobal); - json.startValue(JSON_KEY_APPLICATIONS); - { - json.startArray(); - { - for (String appName : apps) - { - boolean enabled = auditService.isAuditEnabled(appName, path); - json.startObject(); - { - json.writeValue(JSON_KEY_NAME, appName); - json.writeValue(JSON_KEY_PATH, path); - json.writeValue(JSON_KEY_ENABLED, enabled); - } - json.endObject(); - } - } - json.endArray(); - } - json.endValue(); - } - json.endObject(); + model.put(JSON_KEY_ENABLED, enabledGlobal); + model.put(JSON_KEY_APPLICATIONS, appsByName.values()); - // Close off - res.getWriter().close(); - - res.setContentType("application/json"); - res.setContentEncoding(Charset.defaultCharset().displayName()); // TODO: Should be settable on JSONWriter - // res.addHeader("Content-Length", "" + length); // TODO: Do we need this? - res.setStatus(Status.STATUS_OK); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Result: \n\tRequest: " + req + "\n\tModel: " + model); + } + return model; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/audit/AuditControlPost.java b/source/java/org/alfresco/repo/web/scripts/audit/AuditControlPost.java index 3906e82885..1d3b59db16 100644 --- a/source/java/org/alfresco/repo/web/scripts/audit/AuditControlPost.java +++ b/source/java/org/alfresco/repo/web/scripts/audit/AuditControlPost.java @@ -18,11 +18,12 @@ */ package org.alfresco.repo.web.scripts.audit; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptRequest; -import org.springframework.extensions.webscripts.WebScriptResponse; /** * @author Derek Hulley @@ -31,14 +32,16 @@ import org.springframework.extensions.webscripts.WebScriptResponse; public class AuditControlPost extends AbstractAuditWebScript { @Override - public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) { - String app = getApp(req, false); + Map model = new HashMap(7); + + String appName = getAppName(req); String path = getPath(req); boolean enable = getEnableDisable(req); - if (app == null) + if (appName == null) { // Global operation auditService.setAuditEnabled(enable); @@ -46,12 +49,22 @@ public class AuditControlPost extends AbstractAuditWebScript else { // Apply to a specific application - auditService.enableAudit(app, path); + if (enable) + { + auditService.enableAudit(appName, path); + } + else + { + auditService.disableAudit(appName, path); + } } - -// res.setContentType("application/json"); -// res.setContentEncoding(Charset.defaultCharset().displayName()); // TODO: Should be settable on JSONWriter - // res.addHeader("Content-Length", "" + length); // TODO: Do we need this? - res.setStatus(Status.STATUS_OK); + model.put(JSON_KEY_ENABLED, enable); + + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Result: \n\tRequest: " + req + "\n\tModel: " + model); + } + return model; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/audit/AuditWebScriptTest.java b/source/java/org/alfresco/repo/web/scripts/audit/AuditWebScriptTest.java index bf6e9d9395..40c2430960 100644 --- a/source/java/org/alfresco/repo/web/scripts/audit/AuditWebScriptTest.java +++ b/source/java/org/alfresco/repo/web/scripts/audit/AuditWebScriptTest.java @@ -18,15 +18,17 @@ */ package org.alfresco.repo.web.scripts.audit; -import java.util.Set; +import java.util.Map; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.web.scripts.BaseWebScriptTest; import org.alfresco.service.cmr.audit.AuditService; +import org.alfresco.service.cmr.audit.AuditService.AuditApplication; import org.json.JSONArray; import org.json.JSONObject; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.TestWebScriptServer; import org.springframework.extensions.webscripts.TestWebScriptServer.Response; @@ -41,6 +43,7 @@ public class AuditWebScriptTest extends BaseWebScriptTest private ApplicationContext ctx; private AuditService auditService; private String admin; + private boolean globallyEnabled; @Override protected void setUp() throws Exception @@ -51,12 +54,31 @@ public class AuditWebScriptTest extends BaseWebScriptTest admin = AuthenticationUtil.getAdminUserName(); AuthenticationUtil.setFullyAuthenticatedUser(admin); + + globallyEnabled = auditService.isAuditEnabled(); + // Only enable if required + if (!globallyEnabled) + { + auditService.setAuditEnabled(true); + } } @Override protected void tearDown() throws Exception { super.tearDown(); + // Leave audit in correct state + try + { + if (!globallyEnabled) + { + auditService.setAuditEnabled(false); + } + } + catch (Throwable e) + { + throw new RuntimeException("Failed to set audit back to globally enabled/disabled state", e); + } } public void testGetWithoutPermissions() throws Exception @@ -68,55 +90,46 @@ public class AuditWebScriptTest extends BaseWebScriptTest public void testGetIsAuditEnabledGlobally() throws Exception { - boolean checkEnabled = auditService.isAuditEnabled(); - Set checkApps = auditService.getAuditApplications(); + boolean wasEnabled = auditService.isAuditEnabled(); + Map checkApps = auditService.getAuditApplications(); String url = "/api/audit/control"; TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url); - Response response = sendRequest(req, 200, admin); + Response response = sendRequest(req, Status.STATUS_OK, admin); JSONObject json = new JSONObject(response.getContentAsString()); - boolean enabled = json.getBoolean("enabled"); - assertEquals("Mismatched global audit enabled", checkEnabled, enabled); + boolean enabled = json.getBoolean(AbstractAuditWebScript.JSON_KEY_ENABLED); + assertEquals("Mismatched global audit enabled", wasEnabled, enabled); JSONArray apps = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_APPLICATIONS); assertEquals("Incorrect number of applications reported", checkApps.size(), apps.length()); } public void testGetIsAuditEnabledMissingApp() throws Exception { - boolean checkEnabled = auditService.isAuditEnabled(); - - String url = "/api/audit/control?app=xxx"; + String url = "/api/audit/control/xxx"; TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url); - //First, we'll try the request as a simple, non-admin user (expect a 401) - Response response = sendRequest(req, 200, admin); - JSONObject json = new JSONObject(response.getContentAsString()); - boolean enabled = json.getBoolean("enabled"); - assertEquals("Mismatched global audit enabled", checkEnabled, enabled); - JSONArray apps = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_APPLICATIONS); - // We expect that the unknown application is returned with the others - assertEquals("Should not be any apps listed", 0, apps.length()); + sendRequest(req, 404, admin); } - public void testSetAuditEnabled() throws Exception + public void testSetAuditEnabledGlobally() throws Exception { - boolean checkEnabled = auditService.isAuditEnabled(); + boolean wasEnabled = auditService.isAuditEnabled(); // We need to set this back after the test try { - if (checkEnabled) + if (wasEnabled) { - String url = "/api/audit/control/disable"; + String url = "/api/audit/control?enable=false"; TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON); - sendRequest(req, 200, admin); + sendRequest(req, Status.STATUS_OK, admin); } else { - String url = "/api/audit/control/enable"; + String url = "/api/audit/control?enable=true"; TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON); - sendRequest(req, 200, admin); + sendRequest(req, Status.STATUS_OK, admin); } // Check that it worked @@ -124,7 +137,72 @@ public class AuditWebScriptTest extends BaseWebScriptTest } finally { - auditService.setAuditEnabled(checkEnabled); + auditService.setAuditEnabled(wasEnabled); + } + } + + private static final String APP_REPO_NAME = "AlfrescoRepository"; + private static final String APP_REPO_PATH = "/repository"; + public void testGetIsAuditEnabledRepo() throws Exception + { + boolean wasEnabled = auditService.isAuditEnabled(APP_REPO_NAME, null); + + String url = "/api/audit/control/" + APP_REPO_NAME + APP_REPO_PATH; + TestWebScriptServer.GetRequest req = new TestWebScriptServer.GetRequest(url); + + if (wasEnabled) + { + Response response = sendRequest(req, Status.STATUS_OK, admin); + JSONObject json = new JSONObject(response.getContentAsString()); + JSONArray apps = json.getJSONArray(AbstractAuditWebScript.JSON_KEY_APPLICATIONS); + assertEquals("Incorrect number of applications reported", 1, apps.length()); + JSONObject app = apps.getJSONObject(0); + String appName = app.getString(AbstractAuditWebScript.JSON_KEY_NAME); + String appPath = app.getString(AbstractAuditWebScript.JSON_KEY_PATH); + boolean appEnabled = app.getBoolean(AbstractAuditWebScript.JSON_KEY_ENABLED); + assertEquals("Mismatched application audit enabled", wasEnabled, appEnabled); + assertEquals("Mismatched application audit name", APP_REPO_NAME, appName); + assertEquals("Mismatched application audit path", APP_REPO_PATH, appPath); + } + else + { + + } + } + + public void testSetAuditEnabledRepo() throws Exception + { + boolean wasEnabled = auditService.isAuditEnabled(APP_REPO_NAME, APP_REPO_PATH); + + // We need to set this back after the test + try + { + if (wasEnabled) + { + String url = "/api/audit/control/" + APP_REPO_NAME + APP_REPO_PATH + "?enable=false"; + TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON); + sendRequest(req, Status.STATUS_OK, admin); + } + else + { + String url = "/api/audit/control/" + APP_REPO_NAME + APP_REPO_PATH + "?enable=true"; + TestWebScriptServer.PostRequest req = new TestWebScriptServer.PostRequest(url, "", MimetypeMap.MIMETYPE_JSON); + sendRequest(req, Status.STATUS_OK, admin); + } + + // Check that it worked + testGetIsAuditEnabledRepo(); + } + finally + { + if (wasEnabled) + { + auditService.enableAudit(APP_REPO_NAME, APP_REPO_PATH); + } + else + { + auditService.disableAudit(APP_REPO_NAME, APP_REPO_PATH); + } } } }