blob: 550e346821c820d959733c1df1d294c4e43dba5f [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
Matteo Gerola7e180c22017-03-30 11:57:58 +020019import java.io.ByteArrayInputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.nio.charset.StandardCharsets;
23import java.security.KeyManagementException;
24import java.security.KeyStoreException;
25import java.security.NoSuchAlgorithmException;
26import java.security.cert.CertificateException;
27import java.security.cert.X509Certificate;
28import java.util.Base64;
29import java.util.Map;
30import java.util.concurrent.ConcurrentHashMap;
31
32import javax.net.ssl.SSLContext;
33import javax.net.ssl.TrustManager;
34import javax.net.ssl.X509TrustManager;
35import javax.ws.rs.client.Client;
36import javax.ws.rs.client.ClientBuilder;
37import javax.ws.rs.client.Entity;
38import javax.ws.rs.client.WebTarget;
39import javax.ws.rs.core.MediaType;
40import javax.ws.rs.core.Response;
41import javax.ws.rs.core.Response.Status;
42
Hesam Rahimi4a409b42016-08-12 18:37:33 -040043import org.apache.commons.io.IOUtils;
44import org.apache.http.client.methods.HttpPatch;
45import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
46import org.apache.http.entity.StringEntity;
47import org.apache.http.impl.client.CloseableHttpClient;
48import org.apache.http.impl.client.HttpClients;
49import org.apache.http.ssl.SSLContextBuilder;
50import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
51import org.onlab.packet.IpAddress;
52import org.onosproject.net.DeviceId;
53import org.onosproject.protocol.http.HttpSBController;
54import org.onosproject.protocol.rest.RestSBDevice;
55import org.slf4j.Logger;
56import org.slf4j.LoggerFactory;
57
Matteo Gerola7e180c22017-03-30 11:57:58 +020058import com.google.common.collect.ImmutableMap;
Hesam Rahimi4a409b42016-08-12 18:37:33 -040059
60/**
61 * The implementation of HttpSBController.
62 */
63public class HttpSBControllerImpl implements HttpSBController {
64
Matteo Gerola7e180c22017-03-30 11:57:58 +020065 private static final Logger log = LoggerFactory.getLogger(HttpSBControllerImpl.class);
Hesam Rahimi4a409b42016-08-12 18:37:33 -040066 private static final String XML = "xml";
67 private static final String JSON = "json";
Michele Santuaric372c222017-01-12 09:41:25 +010068 protected static final String DOUBLESLASH = "//";
69 protected static final String COLON = ":";
Hesam Rahimi4a409b42016-08-12 18:37:33 -040070 private static final int STATUS_OK = Response.Status.OK.getStatusCode();
71 private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
72 private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
73 private static final String HTTPS = "https";
74 private static final String AUTHORIZATION_PROPERTY = "authorization";
75 private static final String BASIC_AUTH_PREFIX = "Basic ";
76
77 private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
78 private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>();
79
80 public Map<DeviceId, RestSBDevice> getDeviceMap() {
81 return deviceMap;
82 }
83
84 public Map<DeviceId, Client> getClientMap() {
85 return clientMap;
86 }
87
88 @Override
89 public Map<DeviceId, RestSBDevice> getDevices() {
90 return ImmutableMap.copyOf(deviceMap);
91 }
92
93 @Override
94 public RestSBDevice getDevice(DeviceId deviceInfo) {
95 return deviceMap.get(deviceInfo);
96 }
97
98 @Override
99 public RestSBDevice getDevice(IpAddress ip, int port) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200100 return deviceMap.values().stream().filter(v -> v.ip().equals(ip) && v.port() == port).findFirst().get();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400101 }
102
103 @Override
104 public void addDevice(RestSBDevice device) {
105 if (!deviceMap.containsKey(device.deviceId())) {
106 Client client = ignoreSslClient();
107 if (device.username() != null) {
108 String username = device.username();
109 String password = device.password() == null ? "" : device.password();
110 authenticate(client, username, password);
111 }
112 clientMap.put(device.deviceId(), client);
113 deviceMap.put(device.deviceId(), device);
114 } else {
115 log.warn("Trying to add a device that is already existing {}", device.deviceId());
116 }
117
118 }
119
120 @Override
121 public void removeDevice(DeviceId deviceId) {
122 clientMap.remove(deviceId);
123 deviceMap.remove(deviceId);
124 }
125
126 @Override
127 public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200128 return checkStatusCode(post(device, request, payload, typeOfMediaType(mediaType)));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400129 }
130
131 @Override
Matteo Gerola7e180c22017-03-30 11:57:58 +0200132 public int post(DeviceId device, String request, InputStream payload, MediaType mediaType) {
133 Response response = getResponse(device, request, payload, mediaType);
134 if (response == null) {
135 return Status.NO_CONTENT.getStatusCode();
136 }
137 return response.getStatus();
138 }
139
140 @Override
141 public <T> T post(DeviceId device, String request, InputStream payload, String mediaType, Class<T> responseClass) {
142 return post(device, request, payload, typeOfMediaType(mediaType), responseClass);
143 }
144
145 @Override
146 public <T> T post(DeviceId device, String request, InputStream payload, MediaType mediaType,
147 Class<T> responseClass) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400148 Response response = getResponse(device, request, payload, mediaType);
Palash Kalada4798d2017-05-23 20:16:55 +0900149 if (response != null && response.hasEntity()) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400150 return response.readEntity(responseClass);
151 }
152 log.error("Response from device {} for request {} contains no entity", device, request);
153 return null;
154 }
155
Matteo Gerola7e180c22017-03-30 11:57:58 +0200156 private Response getResponse(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400157
158 WebTarget wt = getWebTarget(device, request);
159
160 Response response = null;
161 if (payload != null) {
162 try {
Georgios Katsikas186b9582017-05-31 17:25:54 +0200163 response = wt.request(mediaType).post(
164 Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType)
165 );
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400166 } catch (IOException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200167 log.error("Cannot do POST {} request on device {} because can't read payload", request, device);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400168 }
169 } else {
Georgios Katsikas186b9582017-05-31 17:25:54 +0200170 response = wt.request(mediaType).post(Entity.entity(null, mediaType));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400171 }
172 return response;
173 }
174
175 @Override
176 public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200177 return checkStatusCode(put(device, request, payload, typeOfMediaType(mediaType)));
178 }
179
180 @Override
181 public int put(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400182
183 WebTarget wt = getWebTarget(device, request);
184
185 Response response = null;
186 if (payload != null) {
187 try {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200188 response = wt.request(mediaType.getType()).put(Entity.entity(IOUtils.
189 toString(payload, StandardCharsets.UTF_8), mediaType.getType()));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400190 } catch (IOException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200191 log.error("Cannot do PUT {} request on device {} because can't read payload", request, device);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400192 }
193 } else {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200194 response = wt.request(mediaType.getType()).put(Entity.entity(null, mediaType.getType()));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400195 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200196
197 if (response == null) {
198 return Status.NO_CONTENT.getStatusCode();
199 }
200 return response.getStatus();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400201 }
202
203 @Override
204 public InputStream get(DeviceId device, String request, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200205 return get(device, request, typeOfMediaType(mediaType));
206 }
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400207
Matteo Gerola7e180c22017-03-30 11:57:58 +0200208 @Override
209 public InputStream get(DeviceId device, String request, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400210 WebTarget wt = getWebTarget(device, request);
211
Matteo Gerola7e180c22017-03-30 11:57:58 +0200212 Response s = wt.request(mediaType.getType()).get();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400213
214 if (checkReply(s)) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200215 return new ByteArrayInputStream(s.readEntity((String.class)).getBytes(StandardCharsets.UTF_8));
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400216 }
217 return null;
218 }
219
220 @Override
221 public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200222 return checkStatusCode(patch(device, request, payload, typeOfMediaType(mediaType)));
223 }
224
225 @Override
226 public int patch(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400227
228 try {
229 log.debug("Url request {} ", getUrlString(device, request));
230 HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
231 if (deviceMap.get(device).username() != null) {
232 String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
233 String userPassword = deviceMap.get(device).username() + pwd;
234 String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
235 httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
236 }
237 if (payload != null) {
238 StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
Matteo Gerola7e180c22017-03-30 11:57:58 +0200239 input.setContentType(mediaType.getType());
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400240 httprequest.setEntity(input);
241 }
242 CloseableHttpClient httpClient;
243 if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
244 httpClient = getApacheSslBypassClient();
245 } else {
246 httpClient = HttpClients.createDefault();
247 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200248 return httpClient.execute(httprequest).getStatusLine().getStatusCode();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400249 } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200250 log.error("Cannot do PATCH {} request on device {}", request, device, e);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400251 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200252 return Status.BAD_REQUEST.getStatusCode();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400253 }
254
255 @Override
256 public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200257 return checkStatusCode(delete(device, request, payload, typeOfMediaType(mediaType)));
258 }
259
260 @Override
261 public int delete(DeviceId device, String request, InputStream payload, MediaType mediaType) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400262
263 WebTarget wt = getWebTarget(device, request);
264
Matteo Gerola7e180c22017-03-30 11:57:58 +0200265 // FIXME: do we need to delete an entry by enclosing data in DELETE
266 // request?
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400267 // wouldn't it be nice to use PUT to implement the similar concept?
Matteo Gerola7e180c22017-03-30 11:57:58 +0200268 Response response = wt.request(mediaType.getType()).delete();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400269
Matteo Gerola7e180c22017-03-30 11:57:58 +0200270 return response.getStatus();
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400271 }
272
Matteo Gerola7e180c22017-03-30 11:57:58 +0200273 private MediaType typeOfMediaType(String type) {
274 switch (type) {
275 case XML:
276 return MediaType.APPLICATION_XML_TYPE;
277 case JSON:
278 return MediaType.APPLICATION_JSON_TYPE;
279 default:
280 throw new IllegalArgumentException("Unsupported media type " + type);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400281
282 }
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400283 }
284
285 private void authenticate(Client client, String username, String password) {
286 client.register(HttpAuthenticationFeature.basic(username, password));
287 }
288
289 protected WebTarget getWebTarget(DeviceId device, String request) {
290 log.debug("Sending request to URL {} ", getUrlString(device, request));
291 return clientMap.get(device).target(getUrlString(device, request));
292 }
293
294 //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
295 private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
296 KeyManagementException, KeyStoreException {
297 return HttpClients.custom().
298 setHostnameVerifier(new AllowAllHostnameVerifier()).
299 setSslcontext(new SSLContextBuilder()
300 .loadTrustMaterial(null, (arg0, arg1) -> true)
301 .build()).build();
302 }
303
Michele Santuaric372c222017-01-12 09:41:25 +0100304 protected String getUrlString(DeviceId device, String request) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400305 if (deviceMap.get(device).url() != null) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200306 return deviceMap.get(device).protocol() + COLON + DOUBLESLASH + deviceMap.get(device).url() + request;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400307 } else {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200308 return deviceMap.get(device).protocol() + COLON + DOUBLESLASH + deviceMap.get(device).ip().toString()
309 + COLON + deviceMap.get(device).port() + request;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400310 }
311 }
312
313 private boolean checkReply(Response response) {
314 if (response != null) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200315 boolean statusCode = checkStatusCode(response.getStatus());
316 if (!statusCode && response.hasEntity()) {
317 log.error("Failed request, HTTP error msg : " + response.readEntity(String.class));
318 }
319 return statusCode;
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400320 }
321 log.error("Null reply from device");
322 return false;
323 }
324
325 private boolean checkStatusCode(int statusCode) {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200326 if (statusCode == STATUS_OK || statusCode == STATUS_CREATED || statusCode == STATUS_ACCEPTED) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400327 return true;
328 } else {
Matteo Gerola7e180c22017-03-30 11:57:58 +0200329 log.error("Failed request, HTTP error code : " + statusCode);
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400330 return false;
331 }
332 }
333
334 private Client ignoreSslClient() {
335 SSLContext sslcontext = null;
336
337 try {
338 sslcontext = SSLContext.getInstance("TLS");
339 sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
340 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
341 }
342
343 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
344 }
345
346 public X509Certificate[] getAcceptedIssuers() {
347 return new X509Certificate[0];
348 }
349 } }, new java.security.SecureRandom());
350 } catch (NoSuchAlgorithmException | KeyManagementException e) {
351 e.printStackTrace();
352 }
353
354 return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
355 }
Matteo Gerola7e180c22017-03-30 11:57:58 +0200356
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400357}