/* * Copyright 2016-present Open Networking Laboratory * * 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.protocol.http.ctl; import java.io.ByteArrayInputStream; 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.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.io.IOUtils; 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.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.onlab.packet.IpAddress; import org.onosproject.net.DeviceId; import org.onosproject.protocol.http.HttpSBController; import org.onosproject.protocol.rest.RestSBDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; /** * The implementation of HttpSBController. */ public class HttpSBControllerImpl implements HttpSBController { private static final Logger log = LoggerFactory.getLogger(HttpSBControllerImpl.class); private static final String XML = "xml"; private static final String JSON = "json"; protected static final String DOUBLESLASH = "//"; protected static final String COLON = ":"; 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 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<>(); private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>(); public Map<DeviceId, RestSBDevice> getDeviceMap() { return deviceMap; } public Map<DeviceId, Client> getClientMap() { return clientMap; } @Override public Map<DeviceId, RestSBDevice> getDevices() { return ImmutableMap.copyOf(deviceMap); } @Override public RestSBDevice getDevice(DeviceId deviceInfo) { return deviceMap.get(deviceInfo); } @Override public RestSBDevice getDevice(IpAddress ip, int port) { return deviceMap.values().stream().filter(v -> v.ip().equals(ip) && v.port() == port).findFirst().get(); } @Override 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); } clientMap.put(device.deviceId(), client); deviceMap.put(device.deviceId(), device); } else { log.warn("Trying to add a device that is already existing {}", device.deviceId()); } } @Override public void removeDevice(DeviceId deviceId) { clientMap.remove(deviceId); deviceMap.remove(deviceId); } @Override public boolean post(DeviceId device, String request, InputStream payload, String mediaType) { return checkStatusCode(post(device, request, payload, typeOfMediaType(mediaType))); } @Override public int post(DeviceId device, String request, InputStream payload, MediaType mediaType) { Response response = getResponse(device, request, payload, mediaType); if (response == null) { return Status.NO_CONTENT.getStatusCode(); } return response.getStatus(); } @Override public <T> T post(DeviceId device, String request, InputStream payload, String mediaType, Class<T> responseClass) { return post(device, request, payload, typeOfMediaType(mediaType), responseClass); } @Override public <T> T post(DeviceId device, String request, InputStream payload, MediaType mediaType, Class<T> responseClass) { Response response = getResponse(device, request, payload, mediaType); if (response.hasEntity()) { return response.readEntity(responseClass); } log.error("Response from device {} for request {} contains no entity", device, request); return null; } private Response getResponse(DeviceId device, String request, InputStream payload, MediaType mediaType) { WebTarget wt = getWebTarget(device, request); Response response = null; if (payload != null) { try { response = wt.request(mediaType.getType()) .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType.getType())); } catch (IOException e) { log.error("Cannot do POST {} request on device {} because can't read payload", request, device); } } else { response = wt.request(mediaType.getType()).post(Entity.entity(null, mediaType.getType())); } return response; } @Override public boolean put(DeviceId device, String request, InputStream payload, String mediaType) { return checkStatusCode(put(device, request, payload, typeOfMediaType(mediaType))); } @Override public int put(DeviceId device, String request, InputStream payload, MediaType mediaType) { WebTarget wt = getWebTarget(device, request); Response response = null; if (payload != null) { try { response = wt.request(mediaType.getType()).put(Entity.entity(IOUtils. toString(payload, StandardCharsets.UTF_8), mediaType.getType())); } catch (IOException e) { log.error("Cannot do PUT {} request on device {} because can't read payload", request, device); } } else { response = wt.request(mediaType.getType()).put(Entity.entity(null, mediaType.getType())); } if (response == null) { return Status.NO_CONTENT.getStatusCode(); } return response.getStatus(); } @Override public InputStream get(DeviceId device, String request, String mediaType) { return get(device, request, typeOfMediaType(mediaType)); } @Override public InputStream get(DeviceId device, String request, MediaType mediaType) { WebTarget wt = getWebTarget(device, request); Response s = wt.request(mediaType.getType()).get(); if (checkReply(s)) { return new ByteArrayInputStream(s.readEntity((String.class)).getBytes(StandardCharsets.UTF_8)); } return null; } @Override public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) { return checkStatusCode(patch(device, request, payload, typeOfMediaType(mediaType))); } @Override public int patch(DeviceId device, String request, InputStream payload, MediaType mediaType) { try { log.debug("Url request {} ", getUrlString(device, request)); HttpPatch httprequest = new HttpPatch(getUrlString(device, request)); if (deviceMap.get(device).username() != null) { String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password(); String userPassword = deviceMap.get(device).username() + pwd; 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.getType()); httprequest.setEntity(input); } CloseableHttpClient httpClient; if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) { httpClient = getApacheSslBypassClient(); } else { httpClient = HttpClients.createDefault(); } return httpClient.execute(httprequest).getStatusLine().getStatusCode(); } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { log.error("Cannot do PATCH {} request on device {}", request, device, e); } return Status.BAD_REQUEST.getStatusCode(); } @Override public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) { return checkStatusCode(delete(device, request, payload, typeOfMediaType(mediaType))); } @Override public int delete(DeviceId device, String request, InputStream payload, MediaType mediaType) { WebTarget wt = getWebTarget(device, request); // FIXME: do we need to delete an entry by enclosing data in DELETE // request? // wouldn't it be nice to use PUT to implement the similar concept? Response response = wt.request(mediaType.getType()).delete(); return response.getStatus(); } private MediaType typeOfMediaType(String type) { switch (type) { case XML: return MediaType.APPLICATION_XML_TYPE; case JSON: return MediaType.APPLICATION_JSON_TYPE; default: throw new IllegalArgumentException("Unsupported media type " + type); } } private void authenticate(Client client, String username, String password) { client.register(HttpAuthenticationFeature.basic(username, password)); } protected WebTarget getWebTarget(DeviceId device, String request) { log.debug("Sending request to URL {} ", getUrlString(device, request)); return clientMap.get(device).target(getUrlString(device, request)); } //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(); } protected 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(Response response) { if (response != null) { boolean statusCode = checkStatusCode(response.getStatus()); if (!statusCode && response.hasEntity()) { log.error("Failed request, HTTP error msg : " + response.readEntity(String.class)); } return statusCode; } log.error("Null reply from device"); return false; } private boolean checkStatusCode(int statusCode) { if (statusCode == STATUS_OK || statusCode == STATUS_CREATED || statusCode == STATUS_ACCEPTED) { return true; } else { log.error("Failed request, HTTP error code : " + statusCode); return false; } } private Client ignoreSslClient() { SSLContext sslcontext = null; try { sslcontext = SSLContext.getInstance("TLS"); sslcontext.init(null, new TrustManager[]{new X509TrustManager() { public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }, new java.security.SecureRandom()); } catch (NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); } return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build(); } }