ONOS audit REST API support

Change-Id: Ic2910785f1f16fe5e36b33c1a73f44539cd8fbea
diff --git a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java
index d6d044e..494d7d8 100644
--- a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java
@@ -30,7 +30,8 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.net.DefaultAnnotations;
 import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+
+import static org.slf4j.LoggerFactory.getLogger;
 
 import java.util.Set;
 import java.util.TreeSet;
@@ -39,12 +40,66 @@
  * Base abstraction of Karaf shell commands.
  */
 public abstract class AbstractShellCommand implements Action, CodecContext {
-    protected final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    protected static final Logger log = getLogger(AbstractShellCommand.class);
 
     @Option(name = "-j", aliases = "--json", description = "Output JSON",
             required = false, multiValued = false)
     private boolean json = false;
 
+    private static String auditFile = "all";
+    private static boolean auditEnabled = false;
+
+    /**
+     * To check if CLI Audit is enabled.
+     *
+     * @return true if the CLI Audit is enabled.
+     */
+    private static boolean isEnabled() {
+        return auditEnabled;
+    }
+
+    /**
+     * To enable CLI Audit.
+     */
+    public static void enableAudit() {
+        auditEnabled = true;
+    }
+
+    /**
+     * To disable CLI Audit.
+     */
+    public static void disableAudit() {
+        auditEnabled = false;
+    }
+
+    /**
+     * To set audit file type which CLI Audit logs must be saved.
+     *
+     * @param auditFile file that CLI Audit logs must be saved.
+     */
+    public static void setAuditFile(String auditFile) {
+        AbstractShellCommand.auditFile = auditFile;
+    }
+
+    /**
+     * To save audit logs into the log file.
+     *
+     * @param msg audit message.
+     */
+    private static void saveAuditLog(String msg) {
+        if (isEnabled()) {
+            if (auditFile.equals("all")) {
+                log.info(msg);
+                log.info("AuditLog : " + msg);
+            } else if (auditFile.equals("karaf")) {
+                log.info(msg);
+            } else if (auditFile.equals("audit")) {
+                log.info("AuditLog : " + msg);
+            }
+        }
+    }
+
     /**
      * Returns the reference to the implementation of the specified service.
      *
@@ -54,6 +109,7 @@
      * @throws org.onlab.osgi.ServiceNotFoundException if service is unavailable
      */
     public static <T> T get(Class<T> serviceClass) {
+        saveAuditLog("Audit ");
         return DefaultServiceDirectory.getService(serviceClass);
     }
 
@@ -64,7 +120,7 @@
      */
     protected ApplicationId appId() {
         return get(CoreService.class)
-               .registerApplication("org.onosproject.cli");
+                .registerApplication("org.onosproject.cli");
     }
 
     /**
@@ -126,7 +182,7 @@
     /**
      * Produces a JSON object from the specified key/value annotations.
      *
-     * @param mapper ObjectMapper to use while converting to JSON
+     * @param mapper      ObjectMapper to use while converting to JSON
      * @param annotations key/value annotations
      * @return JSON object
      */
@@ -186,9 +242,9 @@
     /**
      * Generates a Json representation of an object.
      *
-     * @param entity object to generate JSON for
+     * @param entity      object to generate JSON for
      * @param entityClass class to format with - this chooses which codec to use
-     * @param <T> Type of the object being formatted
+     * @param <T>         Type of the object being formatted
      * @return JSON object representation
      */
     public <T> ObjectNode jsonForEntity(T entity, Class<T> entityClass) {
diff --git a/core/net/BUILD b/core/net/BUILD
index fc6c46d..50e0a3f 100644
--- a/core/net/BUILD
+++ b/core/net/BUILD
@@ -3,6 +3,7 @@
     "//utils/rest:onlab-rest",
     "//core/store/serializers:onos-core-serializers",
     "//core/store/primitives:onos-core-primitives",
+    "//cli:onos-cli",
     "@org_osgi_service_cm//jar",
 ]
 
diff --git a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
index dd96525..db5c5e3 100644
--- a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
+++ b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
@@ -124,4 +124,11 @@
 
     public static final String DTP_MAX_BATCH_MS = "maxBatchMs";
     public static final int DTP_MAX_BATCH_MS_DEFAULT = 50;
+
+    public static final String AUDIT_STATUS_DESC = "auditEnabled";
+    public static final boolean AUDIT_STATUS_DEFAULT = false;
+
+    public static final String AUDIT_FILE_TYPE_DESC = "auditFile";
+    public static final String AUDIT_FILE_TYPE_DEFAULT = "all";
+
 }
diff --git a/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java b/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java
new file mode 100644
index 0000000..31b6a18
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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 org.onosproject.net.audit.impl;
+
+import org.onlab.rest.AuditFilter;
+
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cli.AbstractShellCommand;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import java.util.Dictionary;
+
+import static org.onlab.util.Tools.get;
+import static org.onosproject.net.OsgiPropertyConstants.AUDIT_FILE_TYPE_DESC;
+import static org.onosproject.net.OsgiPropertyConstants.AUDIT_FILE_TYPE_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.AUDIT_STATUS_DESC;
+import static org.onosproject.net.OsgiPropertyConstants.AUDIT_STATUS_DEFAULT;
+
+
+/**
+ * Component to manage REST API Audit.
+ */
+@Component(
+        immediate = true,
+        property = {
+                AUDIT_FILE_TYPE_DESC + "=" + AUDIT_FILE_TYPE_DEFAULT,
+                AUDIT_STATUS_DESC + ":Boolean=" + AUDIT_STATUS_DEFAULT
+        })
+public class AuditManager {
+
+    public String auditFile = AUDIT_FILE_TYPE_DEFAULT;
+    public boolean auditEnabled = AUDIT_STATUS_DEFAULT;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ComponentConfigService cfgService;
+
+    @Activate
+    public void activate(ComponentContext context) {
+        cfgService.registerProperties(getClass());
+        setAuditStatus(auditFile, auditEnabled);
+    }
+
+    @Modified
+    protected void modifyFileType(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        if (properties == null) {
+            return;
+        }
+        auditFile = get(properties, AUDIT_FILE_TYPE_DESC);
+        String enableAuditStr = get(properties, AUDIT_STATUS_DESC);
+
+        auditEnabled = Boolean.parseBoolean(enableAuditStr);
+        setAuditStatus(auditFile, auditEnabled);
+    }
+
+    /**
+     * To enable Audit and set file type for REST API and  CLI as  per the changes in configuration properties.
+     *
+     * @param auditFile    file which audit logs are saved.
+     * @param auditEnabled status of REST API Audit and CLI Audit.
+     */
+    public void setAuditStatus(String auditFile, boolean auditEnabled) {
+        if (auditEnabled) {
+            AuditFilter.enableAudit();
+            AbstractShellCommand.enableAudit();
+        } else {
+            AuditFilter.disableAudit();
+            AbstractShellCommand.disableAudit();
+        }
+        AuditFilter.setAuditFile(auditFile);
+        AbstractShellCommand.setAuditFile(auditFile);
+    }
+}
diff --git a/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java b/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java
new file mode 100644
index 0000000..2845808
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of Audit Configuration.
+ */
+package org.onosproject.net.audit.impl;
\ No newline at end of file
diff --git a/tools/package/etc/org.ops4j.pax.logging.cfg b/tools/package/etc/org.ops4j.pax.logging.cfg
new file mode 100644
index 0000000..88688aa
--- /dev/null
+++ b/tools/package/etc/org.ops4j.pax.logging.cfg
@@ -0,0 +1,131 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You 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.
+#
+################################################################################
+
+# Colors for log level rendering
+color.fatal = bright red
+color.error = bright red
+color.warn = bright yellow
+color.info = bright green
+color.debug = cyan
+color.trace = cyan
+
+# Common pattern layout for appenders
+log4j2.pattern = %d{ISO8601} | %-5p | %-16t | %-32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+log4j2.out.pattern = \u001b[90m%d{HH:mm:ss\.SSS}\u001b[0m %highlight{%-5level}{FATAL=${color.fatal}, ERROR=${color.error}, WARN=${color.warn}, INFO=${color.info}, DEBUG=${color.debug}, TRACE=${color.trace}} \u001b[90m[%t]\u001b[0m %msg%n%throwable
+
+
+# Root logger
+log4j2.rootLogger.level = INFO
+# uncomment to use asynchronous loggers, which require mvn:com.lmax/disruptor/3.3.2 library
+#log4j2.rootLogger.type = asyncRoot
+#log4j2.rootLogger.includeLocation = false
+log4j2.rootLogger.appenderRef.RollingFile.ref = RollingFile
+log4j2.rootLogger.appenderRef.AuditFile.ref = AuditFile
+log4j2.rootLogger.appenderRef.PaxOsgi.ref = PaxOsgi
+log4j2.rootLogger.appenderRef.Console.ref = Console
+log4j2.rootLogger.appenderRef.Console.filter.regex.type = RegexFilter
+log4j2.rootLogger.appenderRef.Console.filter.regex.regex = .*Audit.*
+log4j2.rootLogger.appenderRef.Console.filter.regex.onMatch = DENY
+log4j2.rootLogger.appenderRef.Console.filter.regex.onMisMatch = ACCEPT
+#log4j2.rootLogger.appenderRef.Console.filter.threshold.type = ThresholdFilter
+#log4j2.rootLogger.appenderRef.Console.filter.threshold.level = ${karaf.log.console:-OFF}
+
+# Loggers configuration
+
+# SSHD logger
+log4j2.logger.sshd.name = org.apache.sshd
+log4j2.logger.sshd.level = INFO
+
+# Spifly logger
+log4j2.logger.spifly.name = org.apache.aries.spifly
+log4j2.logger.spifly.level = WARN
+
+# Security audit logger
+log4j2.logger.audit.name = audit
+log4j2.logger.audit.level = TRACE
+log4j2.logger.audit.additivity = false
+log4j2.logger.audit.appenderRef.AuditRollingFile.ref = AuditRollingFile
+
+# Appenders configuration
+
+# Console appender not used by default (see log4j2.rootLogger.appenderRefs)
+log4j2.appender.console.type = Console
+log4j2.appender.console.name = Console
+log4j2.appender.console.layout.type = PatternLayout
+log4j2.appender.console.layout.pattern = ${log4j2.out.pattern}
+
+# Rest Audit file appender
+log4j2.appender.auditOnos.type = RollingRandomAccessFile
+log4j2.appender.auditOnos.name = AuditFile
+log4j2.appender.auditOnos.filter.regex.type = RegexFilter
+log4j2.appender.auditOnos.filter.regex.regex = .*AuditLog.*
+log4j2.appender.auditOnos.filter.regex.onMatch = ACCEPT
+log4j2.appender.auditOnos.filter.regex.onMisMatch = DENY
+log4j2.appender.auditOnos.fileName = ${karaf.data}/log/audit.log
+log4j2.appender.auditOnos.filePattern = ${karaf.data}/log/audit-%i.log
+log4j2.appender.auditOnos.append = true
+log4j2.appender.auditOnos.layout.type = PatternLayout
+log4j2.appender.auditOnos.layout.pattern = ${log4j2.pattern}
+log4j2.appender.auditOnos.policies.type = Policies
+log4j2.appender.auditOnos.policies.size.type = SizeBasedTriggeringPolicy
+log4j2.appender.auditOnos.policies.size.size = 8MB
+
+
+# Rolling file appender
+log4j2.appender.rolling.type = RollingRandomAccessFile
+log4j2.appender.rolling.name = RollingFile
+log4j2.appender.rolling.filter.regex.type = RegexFilter
+log4j2.appender.rolling.filter.regex.regex = .*AuditLog.*
+log4j2.appender.rolling.filter.regex.onMatch = DENY
+log4j2.appender.rolling.filter.regex.onMisMatch = ACCEPT
+log4j2.appender.rolling.fileName = ${karaf.data}/log/karaf.log
+log4j2.appender.rolling.filePattern = ${karaf.data}/log/karaf.log.%i
+# uncomment to not force a disk flush
+#log4j2.appender.rolling.immediateFlush = false
+log4j2.appender.rolling.append = true
+log4j2.appender.rolling.layout.type = PatternLayout
+log4j2.appender.rolling.layout.pattern = ${log4j2.pattern}
+log4j2.appender.rolling.policies.type = Policies
+log4j2.appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
+log4j2.appender.rolling.policies.size.size = 16MB
+
+# Audit file appender
+log4j2.appender.audit.type = RollingRandomAccessFile
+log4j2.appender.audit.name = AuditRollingFile
+log4j2.appender.audit.fileName = ${karaf.data}/log/security.log
+log4j2.appender.audit.filePattern = ${karaf.data}/log/security-%i.log
+log4j2.appender.audit.append = true
+log4j2.appender.audit.layout.type = PatternLayout
+log4j2.appender.audit.layout.pattern = %m%n
+log4j2.appender.audit.policies.type = Policies
+log4j2.appender.audit.policies.size.type = SizeBasedTriggeringPolicy
+log4j2.appender.audit.policies.size.size = 8MB
+
+# OSGi appender
+log4j2.appender.osgi.type = PaxOsgi
+log4j2.appender.osgi.name = PaxOsgi
+log4j2.appender.osgi.filter = *
+
+# help with identification of maven-related problems with pax-url-aether
+#log4j2.logger.aether.name = shaded.org.eclipse.aether
+#log4j2.logger.aether.level = TRACE
+#log4j2.logger.http-headers.name = shaded.org.apache.http.headers
+#log4j2.logger.http-headers.level = DEBUG
+#log4j2.logger.maven.name = org.ops4j.pax.url.mvn
+#log4j2.logger.maven.level = TRACE
diff --git a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java
index 41839c7..f6c2739 100644
--- a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java
+++ b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java
@@ -54,6 +54,7 @@
                     WebApplicationExceptionMapper.class,
                     IllegalArgumentExceptionMapper.class,
                     IllegalStateExceptionMapper.class,
+                    AuditFilter.class,
                     JsonBodyWriter.class);
         builder.add(classes);
         return builder.build();
diff --git a/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java b/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java
new file mode 100644
index 0000000..0f098ac
--- /dev/null
+++ b/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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 org.onlab.rest;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import java.io.IOException;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * FIlter for logging all REST Api http requests and details of request and response.
+ */
+
+public class AuditFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+    private static Logger log = getLogger(AuditFilter.class);
+    private ObjectMapper mapper = new ObjectMapper();
+    private final String separator = "  |  ";
+
+    private static boolean disableForTests = false;
+    private static String auditFile = "all";
+    private static boolean auditEnabled = false;
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws IOException {
+        if (disableForTests) {
+            return;
+        }
+        if (isEnabled()) {
+            String requestBody = (requestContext.hasEntity() ?
+                    (readTreeFromStream(mapper, requestContext.getEntityStream()).toString()) : "");
+            requestContext.setProperty("requestBody", requestBody);
+            requestContext.setProperty("auditLog", "Path: " + requestContext.getUriInfo().getPath() + separator
+                    + "Method: " + requestContext.getMethod() + separator
+                    + (requestContext.getMethod().equals("PUT") ?
+                    ("Path_Parameters: " + requestContext.getUriInfo().getPathParameters().toString() + separator
+                            + "Query_Parameters: " + requestContext.getUriInfo().getQueryParameters().toString()
+                            + separator + "Request_Body: " + requestBody) : ""));
+            requestContext.setEntityStream(IOUtils.toInputStream(requestBody));
+        }
+    }
+
+    @Override
+    public void filter(ContainerRequestContext containerRequestContext,
+                       ContainerResponseContext containerResponseContext) throws IOException {
+        if (disableForTests) {
+            return;
+        }
+        if (isEnabled()) {
+            containerRequestContext.setProperty("auditLog", containerRequestContext.getProperty("auditLog") + separator
+                    + "Status: " + containerResponseContext.getStatusInfo().toString());
+            saveAuditLog(containerRequestContext.getProperty("auditLog").toString());
+        }
+    }
+
+    /**
+     * To disable unit testing for this class.
+     */
+    public static void disableForTests() {
+        disableForTests = true;
+    }
+
+    /**
+     * To save audit logs into the log file.
+     *
+     * @param msg audit message.
+     */
+    private void saveAuditLog(String msg) {
+        if (isEnabled()) {
+            if (auditFile.equals("all")) {
+                log.info(msg);
+                log.info("AuditLog : " + msg);
+            } else if (auditFile.equals("karaf")) {
+                log.info(msg);
+            } else if (auditFile.equals("audit")) {
+                log.info("AuditLog : " + msg);
+            }
+        }
+    }
+
+    /**
+     * To check if REST API Audit is enabled.
+     *
+     * @return true if the REST API Audit is enabled.
+     */
+    private static boolean isEnabled() {
+        return auditEnabled;
+    }
+
+    /**
+     * To enable REST API Audit.
+     */
+    public static void enableAudit() {
+        auditEnabled = true;
+    }
+
+    /**
+     * To disable REST API Audit.
+     */
+    public static void disableAudit() {
+        auditEnabled = false;
+    }
+
+    /**
+     * To set audit file type which REST API Audit logs must be saved.
+     *
+     * @param auditFile file that REST API Audit logs are saved.
+     */
+    public static void setAuditFile(String auditFile) {
+        AuditFilter.auditFile = auditFile;
+    }
+
+
+}
diff --git a/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java
index 9f809f3..9ccf53f 100644
--- a/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java
+++ b/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java
@@ -24,6 +24,7 @@
 import org.onlab.junit.TestUtils;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.rest.AuthorizationFilter;
+import org.onlab.rest.AuditFilter;
 import org.onlab.rest.BaseResource;
 
 /**
@@ -51,6 +52,7 @@
     private void configureProperties() {
         set(TestProperties.CONTAINER_PORT, 0);
         AuthorizationFilter.disableForTests();
+        AuditFilter.disableForTests();
     }
 
     /**