ONOS-3810 augmenting Rest southbound protocol and provider for https and password based auth
Change-Id: I3e5f07ba6a751bc8a7637373c037a1910181f9ab
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 baf26cb..aa1b959 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
@@ -16,7 +16,9 @@
package org.onosproject.protocol.rest;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
+import org.apache.commons.lang3.StringUtils;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
@@ -34,18 +36,20 @@
private final String password;
private boolean isActive;
private String protocol;
+ private String url;
public DefaultRestSBDevice(IpAddress ip, int port, String name, String password,
- String protocol, boolean isActive) {
+ String protocol, String url, boolean isActive) {
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");
this.ip = ip;
this.port = port;
this.name = name;
- this.password = password;
+ this.password = StringUtils.isEmpty(password) ? null : password;
this.isActive = isActive;
this.protocol = protocol;
+ this.url = StringUtils.isEmpty(url) ? null : url;
}
@Override
@@ -89,6 +93,22 @@
}
@Override
+ public String url() {
+ return url;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("url", url)
+ .add("protocol", protocol)
+ .add("name", name)
+ .add("port", port)
+ .add("ip", ip)
+ .toString();
+ }
+
+ @Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
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 6b76989..abef64e 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
@@ -79,4 +79,10 @@
*/
String protocol();
+ /**
+ * Returns the url for the REST requests, to be used instead of IP and PORT.
+ *
+ * @return url
+ */
+ String url();
}
diff --git a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
index 0bc3545..0542dec 100644
--- a/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
+++ b/protocols/rest/ctl/src/main/java/org/onosproject/protocol/rest/ctl/RestSBControllerImpl.java
@@ -16,17 +16,22 @@
package org.onosproject.protocol.rest.ctl;
+import com.google.common.collect.ImmutableMap;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.protocol.rest.RestSBController;
@@ -40,6 +45,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -59,7 +68,9 @@
private static final int STATUS_OK = Response.Status.OK.getStatusCode();
private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
- private static final String SLASH = "/";
+ private static final String HTTPS = "https";
+ private static final String AUTHORIZATION_PROPERTY = "authorization";
+ private static final String BASIC_AUTH_PREFIX = "Basic ";
private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
Client client;
@@ -78,7 +89,7 @@
@Override
public Map<DeviceId, RestSBDevice> getDevices() {
- return deviceMap;
+ return ImmutableMap.copyOf(deviceMap);
}
@Override
@@ -88,13 +99,8 @@
@Override
public RestSBDevice getDevice(IpAddress ip, int port) {
- for (RestSBDevice device : deviceMap.values()) {
- if (device.ip().equals(ip) &&
- device.port() == port) {
- return device;
- }
- }
- return null;
+ return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
+ && v.port() == port).findFirst().get();
}
@Override
@@ -162,32 +168,44 @@
throw new IllegalArgumentException("Unsupported media type " + mediaType);
}
- return new ByteArrayInputStream(webResource.accept(type).get(ClientResponse.class)
- .getEntity(String.class)
- .getBytes(StandardCharsets.UTF_8));
+
+ ClientResponse s = webResource.accept(type).get(ClientResponse.class);
+ if (checkReply(s)) {
+ return new ByteArrayInputStream(s.getEntity(String.class)
+ .getBytes(StandardCharsets.UTF_8));
+ }
+ return null;
}
@Override
public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
- String url = deviceMap.get(device).protocol() + COLON +
- DOUBLESLASH +
- deviceMap.get(device).ip().toString() +
- COLON + deviceMap.get(device).port() +
- SLASH + request;
try {
- HttpPatch httprequest = new HttpPatch(url);
+ log.debug("Url request {} ", getUrlString(device, request));
+ HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
+ if (deviceMap.get(device).password() != null) {
+ String userPassword = deviceMap.get(device).name() + COLON + deviceMap.get(device).password();
+ String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
+ httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
+ }
if (payload != null) {
StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
input.setContentType(mediaType);
httprequest.setEntity(input);
}
- int responseStatusCode = HttpClients.createDefault().execute(httprequest)
+ CloseableHttpClient httpClient;
+ if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
+ httpClient = getApacheSslBypassClient();
+ } else {
+ httpClient = HttpClients.createDefault();
+ }
+ int responseStatusCode = httpClient
+ .execute(httprequest)
.getStatusLine()
.getStatusCode();
return checkStatusCode(responseStatusCode);
- } catch (IOException e) {
- log.error("Cannot do PATCH {} request on device {} because can't read payload",
- request, device);
+ } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
+ log.error("Cannot do PATCH {} request on device {}",
+ request, device, e);
}
return false;
}
@@ -212,11 +230,35 @@
}
private WebResource getWebResource(DeviceId device, String request) {
- return Client.create().resource(deviceMap.get(device).protocol() + COLON +
- DOUBLESLASH +
- deviceMap.get(device).ip().toString() +
- COLON + deviceMap.get(device).port() +
- SLASH + request);
+ log.debug("Sending request to URL {} ", getUrlString(device, request));
+ WebResource webResource = client.resource(getUrlString(device, request));
+ if (deviceMap.containsKey(device) && deviceMap.get(device).password() != null) {
+ client.addFilter(new HTTPBasicAuthFilter(deviceMap.get(device).name(),
+ deviceMap.get(device).password()));
+ }
+ return webResource;
+ }
+
+ //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
+ private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
+ KeyManagementException, KeyStoreException {
+ return HttpClients.custom().
+ setHostnameVerifier(new AllowAllHostnameVerifier()).
+ setSslcontext(new SSLContextBuilder()
+ .loadTrustMaterial(null, (arg0, arg1) -> true)
+ .build()).build();
+ }
+
+ private String getUrlString(DeviceId device, String request) {
+ if (deviceMap.get(device).url() != null) {
+ return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
+ + deviceMap.get(device).url() + request;
+ } else {
+ return deviceMap.get(device).protocol() + COLON +
+ DOUBLESLASH +
+ deviceMap.get(device).ip().toString() +
+ COLON + deviceMap.get(device).port() + request;
+ }
}
private boolean checkReply(ClientResponse response) {
@@ -233,7 +275,7 @@
statusCode == STATUS_ACCEPTED) {
return true;
} else {
- log.error("Failed request: HTTP error code : "
+ log.error("Failed request, HTTP error code : "
+ statusCode);
return false;
}
diff --git a/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java b/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
index ab01cac..c7f809d 100644
--- a/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
+++ b/protocols/rest/ctl/src/test/java/org/onosproject/protocol/rest/ctl/RestSBControllerImplTest.java
@@ -39,8 +39,8 @@
public void setUp() {
controller = new RestSBControllerImpl();
controller.activate();
- device1 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.1"), 8080, "foo", "bar", "http", true);
- device2 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.2"), 8080, "foo1", "bar2", "http", true);
+ device1 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.1"), 8080, "foo", "bar", "http", null, true);
+ device2 = new DefaultRestSBDevice(IpAddress.valueOf("127.0.0.2"), 8080, "foo1", "bar2", "http", null, true);
controller.addDevice(device1);
}