blob: b162b56e37e8cf0e42a8623f7c5e2aeeeedb4766 [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
19import com.google.common.collect.ImmutableMap;
20import 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
35import 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 java.io.ByteArrayInputStream;
45import java.io.IOException;
46import java.io.InputStream;
47import java.nio.charset.StandardCharsets;
48import java.security.KeyManagementException;
49import java.security.KeyStoreException;
50import java.security.NoSuchAlgorithmException;
51import java.security.cert.CertificateException;
52import java.security.cert.X509Certificate;
53import java.util.Base64;
54import java.util.Map;
55import java.util.concurrent.ConcurrentHashMap;
56
57/**
58 * The implementation of HttpSBController.
59 */
60public class HttpSBControllerImpl implements HttpSBController {
61
62 private static final Logger log =
63 LoggerFactory.getLogger(HttpSBControllerImpl.class);
64 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) {
98 return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
99 && v.port() == port).findFirst().get();
100 }
101
102 @Override
103 public void addDevice(RestSBDevice device) {
104 if (!deviceMap.containsKey(device.deviceId())) {
105 Client client = ignoreSslClient();
106 if (device.username() != null) {
107 String username = device.username();
108 String password = device.password() == null ? "" : device.password();
109 authenticate(client, username, password);
110 }
111 clientMap.put(device.deviceId(), client);
112 deviceMap.put(device.deviceId(), device);
113 } else {
114 log.warn("Trying to add a device that is already existing {}", device.deviceId());
115 }
116
117 }
118
119 @Override
120 public void removeDevice(DeviceId deviceId) {
121 clientMap.remove(deviceId);
122 deviceMap.remove(deviceId);
123 }
124
125 @Override
126 public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
127 Response response = getResponse(device, request, payload, mediaType);
128 return checkReply(response);
129 }
130
131 @Override
132 public <T> T post(DeviceId device, String request, InputStream payload,
133 String mediaType, Class<T> responseClass) {
134 Response response = getResponse(device, request, payload, mediaType);
135 if (response.hasEntity()) {
136 return response.readEntity(responseClass);
137 }
138 log.error("Response from device {} for request {} contains no entity", device, request);
139 return null;
140 }
141
142 private Response getResponse(DeviceId device, String request, InputStream payload, String mediaType) {
143 String type = typeOfMediaType(mediaType);
144
145 WebTarget wt = getWebTarget(device, request);
146
147 Response response = null;
148 if (payload != null) {
149 try {
150 response = wt.request(type)
151 .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
152 } catch (IOException e) {
153 log.error("Cannot do POST {} request on device {} because can't read payload",
154 request, device);
155 }
156 } else {
157 response = wt.request(type).post(Entity.entity(null, type));
158 }
159 return response;
160 }
161
162 @Override
163 public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
164 String type = typeOfMediaType(mediaType);
165
166 WebTarget wt = getWebTarget(device, request);
167
168 Response response = null;
169 if (payload != null) {
170 try {
171 response = wt.request(type)
172 .put(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), type));
173 } catch (IOException e) {
174 log.error("Cannot do PUT {} request on device {} because can't read payload",
175 request, device);
176 }
177 } else {
178 response = wt.request(type).put(Entity.entity(null, type));
179 }
180 return checkReply(response);
181 }
182
183 @Override
184 public InputStream get(DeviceId device, String request, String mediaType) {
185 String type = typeOfMediaType(mediaType);
186
187 WebTarget wt = getWebTarget(device, request);
188
189 Response s = wt.request(type).get();
190
191 if (checkReply(s)) {
192 return new ByteArrayInputStream(s.readEntity((String.class))
193 .getBytes(StandardCharsets.UTF_8));
194 }
195 return null;
196 }
197
198 @Override
199 public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
200 String type = typeOfMediaType(mediaType);
201
202 try {
203 log.debug("Url request {} ", getUrlString(device, request));
204 HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
205 if (deviceMap.get(device).username() != null) {
206 String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
207 String userPassword = deviceMap.get(device).username() + pwd;
208 String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
209 httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
210 }
211 if (payload != null) {
212 StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
213 input.setContentType(type);
214 httprequest.setEntity(input);
215 }
216 CloseableHttpClient httpClient;
217 if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
218 httpClient = getApacheSslBypassClient();
219 } else {
220 httpClient = HttpClients.createDefault();
221 }
222 int responseStatusCode = httpClient
223 .execute(httprequest)
224 .getStatusLine()
225 .getStatusCode();
226 return checkStatusCode(responseStatusCode);
227 } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
228 log.error("Cannot do PATCH {} request on device {}",
229 request, device, e);
230 }
231 return false;
232 }
233
234 @Override
235 public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
236 String type = typeOfMediaType(mediaType);
237
238 WebTarget wt = getWebTarget(device, request);
239
240 // FIXME: do we need to delete an entry by enclosing data in DELETE request?
241 // wouldn't it be nice to use PUT to implement the similar concept?
242 Response response = wt.request(type).delete();
243
244 return checkReply(response);
245 }
246
247 private String typeOfMediaType(String mediaType) {
248 String type;
249 switch (mediaType) {
250 case XML:
251 type = MediaType.APPLICATION_XML;
252 break;
253 case JSON:
254 type = MediaType.APPLICATION_JSON;
255 break;
256 default:
257 throw new IllegalArgumentException("Unsupported media type " + mediaType);
258
259 }
260 return type;
261 }
262
263 private void authenticate(Client client, String username, String password) {
264 client.register(HttpAuthenticationFeature.basic(username, password));
265 }
266
267 protected WebTarget getWebTarget(DeviceId device, String request) {
268 log.debug("Sending request to URL {} ", getUrlString(device, request));
269 return clientMap.get(device).target(getUrlString(device, request));
270 }
271
272 //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
273 private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
274 KeyManagementException, KeyStoreException {
275 return HttpClients.custom().
276 setHostnameVerifier(new AllowAllHostnameVerifier()).
277 setSslcontext(new SSLContextBuilder()
278 .loadTrustMaterial(null, (arg0, arg1) -> true)
279 .build()).build();
280 }
281
Michele Santuaric372c222017-01-12 09:41:25 +0100282 protected String getUrlString(DeviceId device, String request) {
Hesam Rahimi4a409b42016-08-12 18:37:33 -0400283 if (deviceMap.get(device).url() != null) {
284 return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
285 + deviceMap.get(device).url() + request;
286 } else {
287 return deviceMap.get(device).protocol() + COLON +
288 DOUBLESLASH +
289 deviceMap.get(device).ip().toString() +
290 COLON + deviceMap.get(device).port() + request;
291 }
292 }
293
294 private boolean checkReply(Response response) {
295 if (response != null) {
296 return checkStatusCode(response.getStatus());
297 }
298 log.error("Null reply from device");
299 return false;
300 }
301
302 private boolean checkStatusCode(int statusCode) {
303 if (statusCode == STATUS_OK ||
304 statusCode == STATUS_CREATED ||
305 statusCode == STATUS_ACCEPTED) {
306 return true;
307 } else {
308 log.error("Failed request, HTTP error code : "
309 + statusCode);
310 return false;
311 }
312 }
313
314 private Client ignoreSslClient() {
315 SSLContext sslcontext = null;
316
317 try {
318 sslcontext = SSLContext.getInstance("TLS");
319 sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
320 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
321 }
322
323 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
324 }
325
326 public X509Certificate[] getAcceptedIssuers() {
327 return new X509Certificate[0];
328 }
329 } }, new java.security.SecureRandom());
330 } catch (NoSuchAlgorithmException | KeyManagementException e) {
331 e.printStackTrace();
332 }
333
334 return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
335 }
336}