mirror of
https://github.com/opennetworkinglab/onos.git
synced 2026-05-13 08:46:14 +02:00
Refactoring audit subsystem to clean-up and eliminate back-dependency from core to CLI; still needs additional work.
Change-Id: I93c04c94f27b7b89c582b359eebe125458a573a7
This commit is contained in:
parent
fa066ed2b0
commit
bd8ddfe228
@ -29,6 +29,7 @@ import org.onosproject.net.Annotations;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.onosproject.net.DefaultAnnotations;
|
||||
import org.onosproject.security.AuditService;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
@ -43,63 +44,12 @@ public abstract class AbstractShellCommand implements Action, CodecContext {
|
||||
|
||||
protected static final Logger log = getLogger(AbstractShellCommand.class);
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@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.
|
||||
*
|
||||
@ -109,7 +59,6 @@ public abstract class AbstractShellCommand implements Action, CodecContext {
|
||||
* @throws org.onlab.osgi.ServiceNotFoundException if service is unavailable
|
||||
*/
|
||||
public static <T> T get(Class<T> serviceClass) {
|
||||
saveAuditLog("Audit ");
|
||||
return DefaultServiceDirectory.getService(serviceClass);
|
||||
}
|
||||
|
||||
@ -204,8 +153,9 @@ public abstract class AbstractShellCommand implements Action, CodecContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute() throws Exception {
|
||||
public final Object execute() throws Exception {
|
||||
try {
|
||||
auditCommand();
|
||||
doExecute();
|
||||
} catch (ServiceNotFoundException e) {
|
||||
error(e.getMessage());
|
||||
@ -213,15 +163,23 @@ public abstract class AbstractShellCommand implements Action, CodecContext {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void doExecute() throws Exception {
|
||||
try {
|
||||
execute();
|
||||
} catch (ServiceNotFoundException e) {
|
||||
error(e.getMessage());
|
||||
// Handles auditing
|
||||
private void auditCommand() {
|
||||
AuditService auditService = get(AuditService.class);
|
||||
if (auditService != null && auditService.isAuditing()) {
|
||||
// FIXME: Compose and log audit message here; this is a hack
|
||||
String user = "foo"; // FIXME
|
||||
String action = Thread.currentThread().getName().substring(5); // FIXME
|
||||
auditService.logUserAction(user, action);
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
/**
|
||||
* Body of the shell command.
|
||||
*
|
||||
* @throws Exception thrown when problem is encountered
|
||||
*/
|
||||
protected abstract void doExecute() throws Exception;
|
||||
|
||||
@Override
|
||||
public ObjectMapper mapper() {
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2018-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.security;
|
||||
|
||||
/**
|
||||
* Service for enabling audit logging.
|
||||
*/
|
||||
public interface AuditService {
|
||||
|
||||
/**
|
||||
* Returns true if auditing is enabled.
|
||||
*
|
||||
* @return true if audit enabled; false otherwise
|
||||
*/
|
||||
boolean isAuditing();
|
||||
|
||||
/**
|
||||
* Logs the specified user action.
|
||||
*
|
||||
* @param user user that initiated the action
|
||||
* @param action action being logged
|
||||
*/
|
||||
void logUserAction(String user, String action);
|
||||
|
||||
}
|
||||
@ -1,9 +1,7 @@
|
||||
COMPILE_DEPS = CORE_DEPS + JACKSON + METRICS + KRYO + [
|
||||
"//core/common:onos-core-common",
|
||||
"//utils/rest:onlab-rest",
|
||||
"//core/store/serializers:onos-core-serializers",
|
||||
"//core/store/primitives:onos-core-primitives",
|
||||
"//cli:onos-cli",
|
||||
"@org_osgi_service_cm//jar",
|
||||
]
|
||||
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.audit.impl;
|
||||
|
||||
import org.onosproject.cfg.ComponentConfigService;
|
||||
import org.onosproject.security.AuditService;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Dictionary;
|
||||
|
||||
import static org.onlab.util.Tools.get;
|
||||
import static org.onosproject.net.OsgiPropertyConstants.AUDIT_LOGGER;
|
||||
import static org.onosproject.net.OsgiPropertyConstants.AUDIT_LOGGER_DEFAULT;
|
||||
import static org.onosproject.net.OsgiPropertyConstants.AUDIT_ENABLED;
|
||||
import static org.onosproject.net.OsgiPropertyConstants.AUDIT_ENABLED_DEFAULT;
|
||||
|
||||
/**
|
||||
* Component to manage audit logging.
|
||||
*/
|
||||
@Component(
|
||||
immediate = true,
|
||||
service = { AuditService.class },
|
||||
property = {
|
||||
AUDIT_ENABLED + ":Boolean=" + AUDIT_ENABLED_DEFAULT,
|
||||
AUDIT_LOGGER + "=" + AUDIT_LOGGER_DEFAULT,
|
||||
})
|
||||
public class AuditManager implements AuditService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private Logger auditLog = log;
|
||||
|
||||
/** Specifies whether or not audit logging is enabled. */
|
||||
private boolean auditEnabled = AUDIT_ENABLED_DEFAULT;
|
||||
|
||||
/** Name of the audit logger. */
|
||||
private String auditFile = AUDIT_LOGGER_DEFAULT;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY)
|
||||
protected ComponentConfigService cfgService;
|
||||
|
||||
@Activate
|
||||
protected void activate(ComponentContext ctx) {
|
||||
cfgService.registerProperties(getClass());
|
||||
modified(ctx);
|
||||
log.info("Started");
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate(ComponentContext ctx) {
|
||||
log.info("Stopped");
|
||||
}
|
||||
|
||||
@Modified
|
||||
protected void modified(ComponentContext ctx) {
|
||||
Dictionary<?, ?> properties = ctx.getProperties();
|
||||
if (properties != null) {
|
||||
auditEnabled = Boolean.parseBoolean(get(properties, AUDIT_ENABLED));
|
||||
auditFile = get(properties, AUDIT_LOGGER);
|
||||
auditLog = LoggerFactory.getLogger(auditFile);
|
||||
log.info("Reconfigured; auditEnabled={}; auditFile={}", auditEnabled, auditFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuditing() {
|
||||
return auditEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUserAction(String user, String action) {
|
||||
if (auditEnabled) {
|
||||
auditLog.info("user={}; action={}", user, action);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,4 +17,4 @@
|
||||
/**
|
||||
* Implementation of Audit Configuration.
|
||||
*/
|
||||
package org.onosproject.net.audit.impl;
|
||||
package org.onosproject.audit.impl;
|
||||
@ -125,10 +125,10 @@ public final class OsgiPropertyConstants {
|
||||
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_ENABLED = "auditEnabled";
|
||||
public static final boolean AUDIT_ENABLED_DEFAULT = false;
|
||||
|
||||
public static final String AUDIT_FILE_TYPE_DESC = "auditFile";
|
||||
public static final String AUDIT_FILE_TYPE_DEFAULT = "all";
|
||||
public static final String AUDIT_LOGGER = "auditLogger";
|
||||
public static final String AUDIT_LOGGER_DEFAULT = "securityAudit";
|
||||
|
||||
}
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,6 @@ public abstract class AbstractWebApplication extends Application {
|
||||
WebApplicationExceptionMapper.class,
|
||||
IllegalArgumentExceptionMapper.class,
|
||||
IllegalStateExceptionMapper.class,
|
||||
AuditFilter.class,
|
||||
JsonBodyWriter.class);
|
||||
builder.add(classes);
|
||||
return builder.build();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Foundation
|
||||
* Copyright 2018-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.
|
||||
@ -14,11 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.onlab.rest;
|
||||
package org.onosproject.rest.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.onlab.osgi.DefaultServiceDirectory;
|
||||
import org.onlab.osgi.ServiceDirectory;
|
||||
import org.onosproject.security.AuditService;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
@ -27,34 +29,36 @@ 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.
|
||||
* HTTP Filter for auditing REST API requests.
|
||||
*/
|
||||
|
||||
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;
|
||||
private static ServiceDirectory services = new DefaultServiceDirectory();
|
||||
|
||||
/**
|
||||
* Disables functionality for unit tests.
|
||||
*/
|
||||
public static void disableForTests() {
|
||||
disableForTests = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(ContainerRequestContext requestContext) throws IOException {
|
||||
if (disableForTests) {
|
||||
return;
|
||||
}
|
||||
if (isEnabled()) {
|
||||
if (auditService() != null) {
|
||||
String requestBody = (requestContext.hasEntity() ?
|
||||
(readTreeFromStream(mapper, requestContext.getEntityStream()).toString()) : "");
|
||||
requestContext.setProperty("requestBody", requestBody);
|
||||
requestContext.setProperty("auditLog", "Path: " + requestContext.getUriInfo().getPath() + separator
|
||||
// FIXME: audit message should be better structured
|
||||
requestContext.setProperty("auditMessage", "Path: " + requestContext.getUriInfo().getPath() + separator
|
||||
+ "Method: " + requestContext.getMethod() + separator
|
||||
+ (requestContext.getMethod().equals("PUT") ?
|
||||
// FIXME: is there really a need to differentiate based on method?
|
||||
("Path_Parameters: " + requestContext.getUriInfo().getPathParameters().toString() + separator
|
||||
+ "Query_Parameters: " + requestContext.getUriInfo().getQueryParameters().toString()
|
||||
+ separator + "Request_Body: " + requestBody) : ""));
|
||||
@ -65,72 +69,19 @@ public class AuditFilter implements ContainerRequestFilter, ContainerResponseFil
|
||||
@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());
|
||||
AuditService auditService = auditService();
|
||||
if (auditService != null) {
|
||||
containerRequestContext.setProperty("auditMessage", containerRequestContext.getProperty("auditMessage")
|
||||
+ separator + "Status: " + containerResponseContext.getStatusInfo().toString());
|
||||
// FIXME: Audit record should indicate who did it, not just what was done and when
|
||||
String user = containerRequestContext.getSecurityContext().getUserPrincipal().getName();
|
||||
String action = containerRequestContext.getProperty("auditMessage").toString();
|
||||
auditService.logUserAction(user, action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To disable unit testing for this class.
|
||||
*/
|
||||
public static void disableForTests() {
|
||||
disableForTests = true;
|
||||
private AuditService auditService() {
|
||||
AuditService auditService = disableForTests ? null : services.get(AuditService.class);
|
||||
return auditService != null && auditService.isAuditing() ? auditService : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -53,7 +53,8 @@ public class CoreWebApplication extends AbstractWebApplication {
|
||||
DiagnosticsWebResource.class,
|
||||
UiPreferencesWebResource.class,
|
||||
SystemInfoWebResource.class,
|
||||
PacketProcessorsWebResource.class
|
||||
PacketProcessorsWebResource.class,
|
||||
AuditFilter.class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ import org.glassfish.jersey.test.spi.TestContainerFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user