diff --git a/lib/BUCK b/lib/BUCK index ccc1e15969..5ff711fb74 100644 --- a/lib/BUCK +++ b/lib/BUCK @@ -547,6 +547,15 @@ remote_jar ( visibility = [ 'PUBLIC' ], ) +remote_jar ( + name = 'jersey-security', + out = 'oauth2-client-2.25.1.jar', + url = 'mvn:org.glassfish.jersey.security:oauth2-client:jar:2.25.1', + sha1 = '5081be1cdc45a48ebeada89157cab4711f7bad1b', + maven_coords = 'org.glassfish.jersey.security:oauth2-client:jar:NON-OSGI:2.25.1', + visibility = [ 'PUBLIC' ], +) + remote_jar ( name = 'jersey-common', out = 'jersey-common-2.25.1.jar', diff --git a/lib/deps.json b/lib/deps.json index 7fae453238..f1ff1e9647 100644 --- a/lib/deps.json +++ b/lib/deps.json @@ -155,6 +155,7 @@ "javax.inject": "mvn:org.glassfish.hk2.external:javax.inject:2.5.0-b32", "javax.ws.rs-api": "mvn:javax.ws.rs:javax.ws.rs-api:2.1", "jersey-client": "mvn:org.glassfish.jersey.core:jersey-client:2.25.1", + "jersey-security": "mvn:org.glassfish.jersey.security:oauth2-client:jar:2.25.1", "jersey-common": "mvn:org.glassfish.jersey.core:jersey-common:2.25.1", "jersey-container-jetty-http": "mvn:org.glassfish.jersey.containers:jersey-container-jetty-http:2.25.1", "jersey-container-servlet": "mvn:org.glassfish.jersey.containers:jersey-container-servlet:2.25.1", diff --git a/lib/pom.xml b/lib/pom.xml index 12045a90cb..3398de1716 100644 --- a/lib/pom.xml +++ b/lib/pom.xml @@ -223,6 +223,13 @@ jersey-client ${jersey.version} + + + org.glassfish.jersey.security + oauth2-client + ${jersey.version} + + org.glassfish.jersey.containers jersey-container-servlet diff --git a/protocols/rest/api/BUCK b/protocols/rest/api/BUCK index 74f33b2775..471084fffe 100644 --- a/protocols/rest/api/BUCK +++ b/protocols/rest/api/BUCK @@ -3,6 +3,7 @@ COMPILE_DEPS = [ '//utils/rest:onlab-rest', '//lib:CORE_DEPS', '//lib:jersey-client', + '//lib:jersey-security', '//lib:jersey-common', '//lib:httpclient-osgi', '//lib:httpcore-osgi', diff --git a/protocols/rest/api/pom.xml b/protocols/rest/api/pom.xml index ff5f7de1c2..e334e7ab7b 100644 --- a/protocols/rest/api/pom.xml +++ b/protocols/rest/api/pom.xml @@ -35,6 +35,12 @@ org.glassfish.jersey.core jersey-client + + + org.glassfish.jersey.security + oauth2-client + + org.apache.httpcomponents httpclient-osgi @@ -43,7 +49,6 @@ commons-io commons-io - 2.4 junit diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java index 4a893fcb79..602d989da1 100644 --- a/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java +++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/http/ctl/HttpSBControllerImpl.java @@ -25,10 +25,12 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport; import org.onlab.packet.IpAddress; import org.onosproject.net.DeviceId; import org.onosproject.protocol.http.HttpSBController; import org.onosproject.protocol.rest.RestSBDevice; +import org.onosproject.protocol.rest.RestSBDevice.AuthenticationScheme; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +57,8 @@ import java.util.Base64; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static com.google.common.base.Preconditions.checkNotNull; + /** * The implementation of HttpSBController. */ @@ -102,11 +106,7 @@ public class HttpSBControllerImpl implements HttpSBController { public void addDevice(RestSBDevice device) { if (!deviceMap.containsKey(device.deviceId())) { Client client = ignoreSslClient(); - if (device.username() != null) { - String username = device.username(); - String password = device.password() == null ? "" : device.password(); - authenticate(client, username, password); - } + authenticate(client, device); clientMap.put(device.deviceId(), client); deviceMap.put(device.deviceId(), device); } else { @@ -283,8 +283,23 @@ public class HttpSBControllerImpl implements HttpSBController { } } - private void authenticate(Client client, String username, String password) { - client.register(HttpAuthenticationFeature.basic(username, password)); + private void authenticate(Client client, RestSBDevice device) { + AuthenticationScheme authScheme = device.authentication(); + if (authScheme == AuthenticationScheme.NO_AUTHENTICATION) { + log.debug("{} scheme is specified, ignoring authentication", authScheme); + return; + } else if (authScheme == AuthenticationScheme.OAUTH2) { + String token = checkNotNull(device.token()); + client.register(OAuth2ClientSupport.feature(token)); + } else if (authScheme == AuthenticationScheme.BASIC) { + String username = device.username(); + String password = device.password() == null ? "" : device.password(); + client.register(HttpAuthenticationFeature.basic(username, password)); + } else { + // TODO: Add support for other authentication schemes here. + throw new IllegalArgumentException(String.format("Unsupported authentication scheme: %s", + authScheme.name())); + } } protected WebTarget getWebTarget(DeviceId device, String request) { @@ -343,12 +358,15 @@ public class HttpSBControllerImpl implements HttpSBController { try { sslcontext = SSLContext.getInstance("TLS"); sslcontext.init(null, new TrustManager[]{new X509TrustManager() { + @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } + @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java index 8f59093f74..fef7f3708e 100644 --- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java +++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/DefaultRestSBDevice.java @@ -41,6 +41,8 @@ public class DefaultRestSBDevice implements RestSBDevice { private String protocol; private String url; private boolean isProxy; + private AuthenticationScheme authenticationScheme; + private String token; private final Optional testUrl; private final Optional manufacturer; private final Optional hwVersion; @@ -48,13 +50,13 @@ public class DefaultRestSBDevice implements RestSBDevice { public DefaultRestSBDevice(IpAddress ip, int port, String name, String password, String protocol, String url, boolean isActive) { - this(ip, port, name, password, protocol, url, isActive, "", "", "", ""); + this(ip, port, name, password, protocol, url, isActive, "", "", "", "", AuthenticationScheme.BASIC, ""); } public DefaultRestSBDevice(IpAddress ip, int port, String name, String password, String protocol, String url, boolean isActive, String testUrl, String manufacturer, - String hwVersion, - String swVersion) { + String hwVersion, String swVersion, AuthenticationScheme authenticationScheme, + String token) { Preconditions.checkNotNull(ip, "IP address cannot be null"); Preconditions.checkArgument(port > 0, "Port address cannot be negative"); Preconditions.checkNotNull(protocol, "protocol address cannot be null"); @@ -65,6 +67,8 @@ public class DefaultRestSBDevice implements RestSBDevice { this.isActive = isActive; this.protocol = protocol; this.url = StringUtils.isEmpty(url) ? null : url; + this.authenticationScheme = authenticationScheme; + this.token = token; this.manufacturer = StringUtils.isEmpty(manufacturer) ? Optional.empty() : Optional.ofNullable(manufacturer); this.hwVersion = StringUtils.isEmpty(hwVersion) ? @@ -158,6 +162,16 @@ public class DefaultRestSBDevice implements RestSBDevice { return swVersion; } + @Override + public AuthenticationScheme authentication() { + return authenticationScheme; + } + + @Override + public String token() { + return token; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -168,6 +182,8 @@ public class DefaultRestSBDevice implements RestSBDevice { .add("username", username) .add("port", port) .add("ip", ip) + .add("authentication", authenticationScheme.name()) + .add("token", token) .add("manufacturer", manufacturer.orElse(null)) .add("hwVersion", hwVersion.orElse(null)) .add("swVersion", swVersion.orElse(null)) diff --git a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java index edcce6e335..6767b2d1f2 100644 --- a/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java +++ b/protocols/rest/api/src/main/java/org/onosproject/protocol/rest/RestSBDevice.java @@ -25,6 +25,16 @@ import java.util.Optional; * Represents an abstraction of a Rest Device in ONOS. */ public interface RestSBDevice { + /** + * REST Authentication schemes. + */ + public enum AuthenticationScheme { + NO_AUTHENTICATION, + BASIC, + OAUTH, + OAUTH2, + } + /** * Returns the ip of this device. * @@ -39,6 +49,20 @@ public interface RestSBDevice { */ int port(); + /** + * The authentication scheme of rest device. + * + * @return authentication + */ + AuthenticationScheme authentication(); + + /** + * The access token of rest device if authentication is OAuth2. + * + * @return token + */ + String token(); + /** * Returns the username of this device. * @@ -91,6 +115,7 @@ public interface RestSBDevice { /** * Returns the proxy state of this device * (if true, the device is proxying multiple ONOS devices). + * * @return proxy state */ boolean isProxy(); @@ -122,4 +147,5 @@ public interface RestSBDevice { * @return the software version. */ Optional swVersion(); + } diff --git a/providers/rest/BUCK b/providers/rest/BUCK index 81fe5b4ed9..b6e2e2b695 100644 --- a/providers/rest/BUCK +++ b/providers/rest/BUCK @@ -3,6 +3,7 @@ BUNDLES = [ '//protocols/rest/api:onos-protocols-rest-api', '//protocols/rest/ctl:onos-protocols-rest-ctl', '//lib:jersey-client', + '//lib:jersey-security', '//lib:commons-io', '//lib:httpclient-osgi', '//lib:httpcore-osgi', diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceConfig.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceConfig.java index 648e56a5c7..cd290e5cdf 100644 --- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceConfig.java +++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceConfig.java @@ -21,6 +21,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.onlab.packet.IpAddress; import org.onosproject.net.DeviceId; import org.onosproject.net.config.Config; +import org.onosproject.protocol.rest.RestSBDevice.AuthenticationScheme; /** * Configuration to push devices to the REST provider. @@ -38,11 +39,14 @@ public class RestDeviceConfig extends Config { private static final String MANUFACTURER = "manufacturer"; private static final String HWVERSION = "hwVersion"; private static final String SWVERSION = "swVersion"; + private static final String AUTHENTICATION_SCHEME = "authenticationScheme"; + private static final String TOKEN = "token"; @Override public boolean isValid() { return hasOnlyFields(IP, PORT, USERNAME, PASSWORD, PROTOCOL, URL, - TESTURL, MANUFACTURER, HWVERSION, SWVERSION) && + TESTURL, MANUFACTURER, HWVERSION, SWVERSION, AUTHENTICATION_SCHEME, + TOKEN) && ip() != null; } @@ -136,6 +140,31 @@ public class RestDeviceConfig extends Config { return get(SWVERSION, ""); } + /** + * Gets the authentication type of the REST device. + * Default is 'basic' if username is defined, else default is no_authentication. + * + * @return authentication + */ + public AuthenticationScheme authenticationScheme() { + // hack for backward compatibility + if (!hasField(AUTHENTICATION_SCHEME)) { + if (hasField(USERNAME)) { + return AuthenticationScheme.BASIC; + } + } + return AuthenticationScheme.valueOf(get(AUTHENTICATION_SCHEME, "NO_AUTHENTICATION").toUpperCase()); + } + + /** + * Gets the token of the REST device. + * + * @return token + */ + public String token() { + return get(TOKEN, ""); + } + private Pair extractIpPort() { String info = subject.toString(); if (info.startsWith(RestDeviceProvider.REST)) { diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java index 091dc346ee..7f6d9e829b 100644 --- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java +++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java @@ -28,8 +28,6 @@ import org.onlab.util.SharedScheduledExecutorService; import org.onlab.util.SharedScheduledExecutors; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; -import org.onosproject.net.behaviour.PortAdmin; -import org.onosproject.net.config.ConfigException; import org.onosproject.net.AnnotationKeys; import org.onosproject.net.DefaultAnnotations; import org.onosproject.net.Device; @@ -38,7 +36,9 @@ import org.onosproject.net.MastershipRole; import org.onosproject.net.PortNumber; import org.onosproject.net.SparseAnnotations; import org.onosproject.net.behaviour.DevicesDiscovery; +import org.onosproject.net.behaviour.PortAdmin; import org.onosproject.net.behaviour.PortDiscovery; +import org.onosproject.net.config.ConfigException; import org.onosproject.net.config.ConfigFactory; import org.onosproject.net.config.NetworkConfigEvent; import org.onosproject.net.config.NetworkConfigListener; @@ -73,6 +73,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -81,7 +82,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; -import java.util.concurrent.CompletableFuture; import static com.google.common.base.Preconditions.checkNotNull; import static org.onlab.util.Tools.groupedThreads; @@ -354,22 +354,25 @@ public class RestDeviceProvider extends AbstractProvider Set deviceSubjects = cfgService.getSubjects(DeviceId.class, RestDeviceConfig.class); connectDevices(deviceSubjects.stream() - .filter(deviceId -> deviceService.getDevice(deviceId) == null) - .map(deviceId -> { - RestDeviceConfig config = - cfgService.getConfig(deviceId, RestDeviceConfig.class); - return new DefaultRestSBDevice(config.ip(), - config.port(), - config.username(), - config.password(), - config.protocol(), - config.url(), - false, - config.testUrl(), - config.manufacturer(), - config.hwVersion(), - config.swVersion()); - }).collect(Collectors.toSet())); + .filter(deviceId -> deviceService.getDevice(deviceId) == null) + .map(deviceId -> { + RestDeviceConfig config = + cfgService.getConfig(deviceId, RestDeviceConfig.class); + return new DefaultRestSBDevice(config.ip(), + config.port(), + config.username(), + config.password(), + config.protocol(), + config.url(), + false, + config.testUrl(), + config.manufacturer(), + config.hwVersion(), + config.swVersion(), + config.authenticationScheme(), + config.token() + ); + }).collect(Collectors.toSet())); } //Old method to register devices provided via net-cfg under apps/rest/ tree diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java index e04be6f137..21d9774225 100644 --- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java +++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestProviderConfig.java @@ -21,15 +21,17 @@ import com.google.common.annotations.Beta; import com.google.common.collect.Sets; import org.onlab.packet.IpAddress; import org.onosproject.core.ApplicationId; -import org.onosproject.net.config.ConfigException; import org.onosproject.net.config.Config; +import org.onosproject.net.config.ConfigException; import org.onosproject.protocol.rest.DefaultRestSBDevice; import org.onosproject.protocol.rest.RestSBDevice; +import org.onosproject.protocol.rest.RestSBDevice.AuthenticationScheme; import java.util.Set; /** * Configuration for RestSB provider. + * * @deprecated 1.10.0 Kingfisher. Please Use RestDeviceConfig */ @Deprecated @@ -48,6 +50,8 @@ public class RestProviderConfig extends Config { private static final String MANUFACTURER = "manufacturer"; private static final String HWVERSION = "hwVersion"; private static final String SWVERSION = "swVersion"; + private static final String AUTHENTICATION_SCHEME = "authenticationScheme"; + private static final String TOKEN = "token"; public Set getDevicesAddresses() throws ConfigException { Set devicesAddresses = Sets.newHashSet(); @@ -65,11 +69,14 @@ public class RestProviderConfig extends Config { String manufacturer = node.path(MANUFACTURER).asText(); String hwVersion = node.path(HWVERSION).asText(); String swVersion = node.path(SWVERSION).asText(); + AuthenticationScheme authenticationScheme = AuthenticationScheme.valueOf(node.path( + AUTHENTICATION_SCHEME).asText().toUpperCase()); + String token = node.path(TOKEN).asText(); devicesAddresses.add(new DefaultRestSBDevice(ipAddr, port, username, - password, protocol, - url, false, testUrl, manufacturer, - hwVersion, swVersion)); + password, protocol, + url, false, testUrl, manufacturer, + hwVersion, swVersion, authenticationScheme, token)); } } catch (IllegalArgumentException e) { throw new ConfigException(CONFIG_VALUE_ERROR, e); diff --git a/web/api/BUCK b/web/api/BUCK index 69c1cbf628..c1c6ab41a2 100644 --- a/web/api/BUCK +++ b/web/api/BUCK @@ -10,6 +10,7 @@ COMPILE_DEPS = [ TEST_DEPS = [ '//lib:TEST_REST', '//lib:minimal-json', + '//lib:jersey-security' ] osgi_jar_with_tests (