blob: 79c429eadff80800cbc0ce0c6be4ac32daed64d1 [file] [log] [blame]
Hesam Rahimi4a409b42016-08-12 18:37:33 -04001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.protocol.http.ctl;
18
Michal Machbcd58c72017-06-19 17:12:34 +020019import com.google.common.collect.ImmutableMap;
Hesam Rahimi4a409b42016-08-12 18:37:33 -040020import org.apache.commons.io.IOUtils;
21import org.apache.http.client.methods.HttpPatch;
22import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
23import org.apache.http.entity.StringEntity;
24import org.apache.http.impl.client.CloseableHttpClient;
25import org.apache.http.impl.client.HttpClients;
26import org.apache.http.ssl.SSLContextBuilder;
27import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
28import org.onlab.packet.IpAddress;
29import org.onosproject.net.DeviceId;
30import org.onosproject.protocol.http.HttpSBController;
31import org.onosproject.protocol.rest.RestSBDevice;
32import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
Michal Machbcd58c72017-06-19 17:12:34 +020035import javax.net.ssl.SSLContext;
36import javax.net.ssl.TrustManager;
37import javax.net.ssl.X509TrustManager;
38import javax.ws.rs.client.Client;
39import javax.ws.rs.client.ClientBuilder;
40import javax.ws.rs.client.Entity;
41import javax.ws.rs.client.WebTarget;
42import javax.ws.rs.core.MediaType;
43import javax.ws.rs.core.Response;
44import javax.ws.rs.core.Response.Status;
45import java.io.ByteArrayInputStream;
46import java.io.IOException;
47import java.io.InputStream;
48import java.nio.charset.StandardCharsets;
49import java.security.KeyManagementException;
50import java.security.KeyStoreException;
51import java.security.NoSuchAlgorithmException;
52import java.security.cert.CertificateException;
53import java.security.cert.X509Certificate;
54import java.util.Base64;
55import java.util.Map;
56import java.util.concurrent.ConcurrentHashMap;
Hesam Rahimi4a409b42016-08-12 18:37:33 -040057
58/**
59 * The implementation of HttpSBController.
60 */
61public class HttpSBControllerImpl implements HttpSBController {
62
Matteo Gerola7e180c22017-03-30 11:57:58 +020063 private static final Logger log = LoggerFactory.getLogger(HttpSBControllerImpl.class);
Hesam Rahimi4a409b42016-08-12 18:37:33 -040064 private static final String XML = "xml";
65 private static final String JSON = "json";
Michele Santuaric372c222017-01-12 09:41:25 +010066 protected static final String DOUBLESLASH = "//";
67 protected static final String COLON = ":";
Hesam Rahimi4a409b42016-08-12 18:37:33 -040068 private static final int STATUS_OK = Response.Status.OK.getStatusCode();
69 private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
70 private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
71 private static final String HTTPS = "https";
72 private static final String AUTHORIZATION_PROPERTY = "authorization";
73 private static final String BASIC_AUTH_PREFIX = "Basic ";
74
75 private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
76 private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>();
77
78 public Map<DeviceId, RestSBDevice> getDeviceMap() {
79 return deviceMap;
80 }
81
82 public Map<DeviceId, Client> getClientMap() {
83 return clientMap;
84 }
85
86 @Override
87 public Map<DeviceId, RestSBDevice> getDevices() {
88 return ImmutableMap.copyOf(deviceMap);
89 }
90
91 @Override
92 public RestSBDevice getDevice(DeviceId deviceInfo) {
93 return deviceMap.get(deviceInfo);
94 }
95
96 @Override
97 public RestSBDevice getDevice(IpAddress ip, int port) {
Matteo Gerola7e180c22017-03-30 11:57:58 +020098 return deviceMap.values().stream().filter(v -> v.ip().equals(ip) && v.port() == port).findFirst().get();
Hesam Rahimi4a409b42016-08-12 18:37:33 -040099 }
100
101 @Override
102 public void addDevice(RestSBDevice device) {
103 if (!deviceMap.containsKey(device.deviceId())) {
104 Client client = ignoreSslClient();
105 if (device.username() != null) {
106 String username = device.username();
107 String password = device.password() == null ? "" : device.password();
108 authenticate(client, username, password);
109 }
110 clientMap.put(device.deviceId(), client);
111 deviceMap.put(device.deviceId(), device);
112 } else {
113 log.warn("Trying to add a device that is already existing {}", device.deviceId());
114 }
115
116 }
117
118 @Override
119 public void removeDevice(DeviceId deviceId) {
120 clientMap.remove(deviceId);
121 deviceMap.remove(deviceId);
122 }
123
124 @Override
125 public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200126 return checkStatusCode(post(device, request, payload, typeOfMediaType(mediaType)));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400127 }
128
129 @Override
Matteo Gerola7e180c22017-03-30 11:57:58 +0200130 public int post(DeviceId device, String request, InputStream payload, MediaType mediaType) {
131 Response response = getResponse(device, request, payload, mediaType);
132 if (response == null) {
133 return Status.NO_CONTENT.getStatusCode();
134 }
135 return response.getStatus();
136 }
137
138 @Override
139 public <T> T post(DeviceId device, String request, InputStream payload, String mediaType, Class<T> responseClass) {
140 return post(device, request, payload, typeOfMediaType(mediaType), responseClass);
141 }
142
143 @Override
144 public <T> T post(DeviceId device, String request, InputStream payload, MediaType mediaType,
145 Class<T> responseClass) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400146 Response response = getResponse(device, request, payload, mediaType);
Palash Kalada4798d2017-05-23 20:16:55 +0900147 if (response != null && response.hasEntity()) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400148 return response.readEntity(responseClass);
149 }
150 log.error("Response from device {} for request {} contains no entity", device, request);
151 return null;
152 }
153
Matteo Gerola7e180c22017-03-30 11:57:58 +0200154 private Response getResponse(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400155
156 WebTarget wt = getWebTarget(device, request);
157
158 Response response = null;
159 if (payload != null) {
160 try {
Eunjin Choi51244d32017-05-15 14:09:56 +0900161 response = wt.request(mediaType)
162 .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400163 } catch (IOException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200164 log.error("Cannot do POST {} request on device {} because can't read payload", request, device);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400165 }
166 } else {
Georgios Katsikas186b9582017-05-31 17:25:54 +0200167 response = wt.request(mediaType).post(Entity.entity(null, mediaType));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400168 }
169 return response;
170 }
171
172 @Override
173 public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200174 return checkStatusCode(put(device, request, payload, typeOfMediaType(mediaType)));
175 }
176
177 @Override
178 public int put(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400179
180 WebTarget wt = getWebTarget(device, request);
181
182 Response response = null;
183 if (payload != null) {
184 try {
Eunjin Choi51244d32017-05-15 14:09:56 +0900185 response = wt.request(mediaType).put(Entity.entity(IOUtils.
186 toString(payload, StandardCharsets.UTF_8), mediaType));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400187 } catch (IOException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200188 log.error("Cannot do PUT {} request on device {} because can't read payload", request, device);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400189 }
190 } else {
Eunjin Choi51244d32017-05-15 14:09:56 +0900191 response = wt.request(mediaType).put(Entity.entity(null, mediaType));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400192 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200193
194 if (response == null) {
195 return Status.NO_CONTENT.getStatusCode();
196 }
197 return response.getStatus();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400198 }
199
200 @Override
201 public InputStream get(DeviceId device, String request, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200202 return get(device, request, typeOfMediaType(mediaType));
203 }
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400204
Matteo Gerola7e180c22017-03-30 11:57:58 +0200205 @Override
206 public InputStream get(DeviceId device, String request, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400207 WebTarget wt = getWebTarget(device, request);
208
Eunjin Choi51244d32017-05-15 14:09:56 +0900209 Response s = wt.request(mediaType).get();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400210
211 if (checkReply(s)) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200212 return new ByteArrayInputStream(s.readEntity((String.class)).getBytes(StandardCharsets.UTF_8));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400213 }
214 return null;
215 }
216
217 @Override
218 public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200219 return checkStatusCode(patch(device, request, payload, typeOfMediaType(mediaType)));
220 }
221
222 @Override
223 public int patch(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400224
225 try {
226 log.debug("Url request {} ", getUrlString(device, request));
227 HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
228 if (deviceMap.get(device).username() != null) {
229 String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
230 String userPassword = deviceMap.get(device).username() + pwd;
231 String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
232 httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
233 }
234 if (payload != null) {
235 StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
Eunjin Choi51244d32017-05-15 14:09:56 +0900236 input.setContentType(mediaType.toString());
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400237 httprequest.setEntity(input);
238 }
239 CloseableHttpClient httpClient;
240 if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
241 httpClient = getApacheSslBypassClient();
242 } else {
243 httpClient = HttpClients.createDefault();
244 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200245 return httpClient.execute(httprequest).getStatusLine().getStatusCode();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400246 } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200247 log.error("Cannot do PATCH {} request on device {}", request, device, e);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400248 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200249 return Status.BAD_REQUEST.getStatusCode();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400250 }
251
252 @Override
253 public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200254 return checkStatusCode(delete(device, request, payload, typeOfMediaType(mediaType)));
255 }
256
257 @Override
258 public int delete(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400259
260 WebTarget wt = getWebTarget(device, request);
261
Matteo Gerola7e180c22017-03-30 11:57:58 +0200262 // FIXME: do we need to delete an entry by enclosing data in DELETE
263 // request?
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400264 // wouldn't it be nice to use PUT to implement the similar concept?
Eunjin Choi51244d32017-05-15 14:09:56 +0900265 Response response = wt.request(mediaType).delete();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400266
Matteo Gerola7e180c22017-03-30 11:57:58 +0200267 return response.getStatus();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400268 }
269
Matteo Gerola7e180c22017-03-30 11:57:58 +0200270 private MediaType typeOfMediaType(String type) {
271 switch (type) {
272 case XML:
273 return MediaType.APPLICATION_XML_TYPE;
274 case JSON:
275 return MediaType.APPLICATION_JSON_TYPE;
Michal Machf0ce45e2017-06-20 11:54:08 +0200276 case MediaType.WILDCARD:
277 return MediaType.WILDCARD_TYPE;
Matteo Gerola7e180c22017-03-30 11:57:58 +0200278 default:
279 throw new IllegalArgumentException("Unsupported media type " + type);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400280
281 }
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400282 }
283
284 private void authenticate(Client client, String username, String password) {
285 client.register(HttpAuthenticationFeature.basic(username, password));
286 }
287
288 protected WebTarget getWebTarget(DeviceId device, String request) {
289 log.debug("Sending request to URL {} ", getUrlString(device, request));
290 return clientMap.get(device).target(getUrlString(device, request));
291 }
292
293 //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
294 private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
295 KeyManagementException, KeyStoreException {
296 return HttpClients.custom().
297 setHostnameVerifier(new AllowAllHostnameVerifier()).
298 setSslcontext(new SSLContextBuilder()
299 .loadTrustMaterial(null, (arg0, arg1) -> true)
300 .build()).build();
301 }
302
Michal Machbcd58c72017-06-19 17:12:34 +0200303 protected String getUrlString(DeviceId deviceId, String request) {
304 RestSBDevice restSBDevice = deviceMap.get(deviceId);
305 if (restSBDevice == null) {
306 log.warn("restSbDevice cannot be NULL!");
307 return "";
308 }
309 if (restSBDevice.url() != null) {
310 return restSBDevice.protocol() + COLON + DOUBLESLASH + restSBDevice.url() + request;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400311 } else {
Michal Machbcd58c72017-06-19 17:12:34 +0200312 return restSBDevice.protocol() + COLON + DOUBLESLASH + restSBDevice.ip().toString()
313 + COLON + restSBDevice.port() + request;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400314 }
315 }
316
317 private boolean checkReply(Response response) {
318 if (response != null) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200319 boolean statusCode = checkStatusCode(response.getStatus());
320 if (!statusCode && response.hasEntity()) {
321 log.error("Failed request, HTTP error msg : " + response.readEntity(String.class));
322 }
323 return statusCode;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400324 }
325 log.error("Null reply from device");
326 return false;
327 }
328
329 private boolean checkStatusCode(int statusCode) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200330 if (statusCode == STATUS_OK || statusCode == STATUS_CREATED || statusCode == STATUS_ACCEPTED) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400331 return true;
332 } else {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200333 log.error("Failed request, HTTP error code : " + statusCode);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400334 return false;
335 }
336 }
337
338 private Client ignoreSslClient() {
339 SSLContext sslcontext = null;
340
341 try {
342 sslcontext = SSLContext.getInstance("TLS");
343 sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
344 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
345 }
346
347 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
348 }
349
350 public X509Certificate[] getAcceptedIssuers() {
351 return new X509Certificate[0];
352 }
353 } }, new java.security.SecureRandom());
354 } catch (NoSuchAlgorithmException | KeyManagementException e) {
355 e.printStackTrace();
356 }
357
358 return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
359 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200360
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400361}