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);
     }