blob: 47372cd79a36251a6f45ea2a9089bae558c9bf2e [file] [log] [blame]
Andrea Campanella945ded22016-01-07 13:17:43 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Andrea Campanella945ded22016-01-07 13:17:43 -08003 *
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.rest.ctl;
18
Andrea Campanella2947e622016-01-27 09:23:46 -080019import com.google.common.collect.ImmutableMap;
Andrea Campanella945ded22016-01-07 13:17:43 -080020import org.apache.commons.io.IOUtils;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Service;
Andrea Campanellace279ee2016-01-25 10:21:45 -080025import org.apache.http.client.methods.HttpPatch;
Andrea Campanella2947e622016-01-27 09:23:46 -080026import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
Andrea Campanellace279ee2016-01-25 10:21:45 -080027import org.apache.http.entity.StringEntity;
Andrea Campanella2947e622016-01-27 09:23:46 -080028import org.apache.http.impl.client.CloseableHttpClient;
Andrea Campanellace279ee2016-01-25 10:21:45 -080029import org.apache.http.impl.client.HttpClients;
Andrea Campanella2947e622016-01-27 09:23:46 -080030import org.apache.http.ssl.SSLContextBuilder;
Jian Li9d616492016-03-09 10:52:49 -080031import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
Andrea Campanella945ded22016-01-07 13:17:43 -080032import org.onlab.packet.IpAddress;
33import org.onosproject.net.DeviceId;
34import org.onosproject.protocol.rest.RestSBController;
35import org.onosproject.protocol.rest.RestSBDevice;
Andrea Campanella945ded22016-01-07 13:17:43 -080036import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
Andrea Campanellac6ecc632016-03-10 17:57:06 -080039import javax.net.ssl.SSLContext;
40import javax.net.ssl.TrustManager;
41import javax.net.ssl.X509TrustManager;
Jian Li9d616492016-03-09 10:52:49 -080042import javax.ws.rs.client.Client;
43import javax.ws.rs.client.ClientBuilder;
44import javax.ws.rs.client.Entity;
45import javax.ws.rs.client.WebTarget;
Andrea Campanella945ded22016-01-07 13:17:43 -080046import javax.ws.rs.core.MediaType;
47import javax.ws.rs.core.Response;
Andrea Campanellac6ecc632016-03-10 17:57:06 -080048import java.io.ByteArrayInputStream;
Andrea Campanella945ded22016-01-07 13:17:43 -080049import java.io.IOException;
50import java.io.InputStream;
51import java.nio.charset.StandardCharsets;
Andrea Campanella2947e622016-01-27 09:23:46 -080052import java.security.KeyManagementException;
53import java.security.KeyStoreException;
54import java.security.NoSuchAlgorithmException;
Andrea Campanellac6ecc632016-03-10 17:57:06 -080055import java.security.cert.CertificateException;
56import java.security.cert.X509Certificate;
Andrea Campanella2947e622016-01-27 09:23:46 -080057import java.util.Base64;
Andrea Campanella945ded22016-01-07 13:17:43 -080058import java.util.Map;
59import java.util.concurrent.ConcurrentHashMap;
60
61/**
62 * The implementation of RestSBController.
63 */
64@Component(immediate = true)
65@Service
66public class RestSBControllerImpl implements RestSBController {
67
68 private static final Logger log =
69 LoggerFactory.getLogger(RestSBControllerImpl.class);
Andrea Campanella945ded22016-01-07 13:17:43 -080070 private static final String XML = "xml";
71 private static final String JSON = "json";
72 private static final String DOUBLESLASH = "//";
73 private static final String COLON = ":";
74 private static final int STATUS_OK = Response.Status.OK.getStatusCode();
75 private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
76 private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
Andrea Campanella2947e622016-01-27 09:23:46 -080077 private static final String HTTPS = "https";
78 private static final String AUTHORIZATION_PROPERTY = "authorization";
79 private static final String BASIC_AUTH_PREFIX = "Basic ";
Andrea Campanella945ded22016-01-07 13:17:43 -080080
81 private final Map<DeviceId, RestSBDevice> deviceMap = new ConcurrentHashMap<>();
Andrea Campanellac6ecc632016-03-10 17:57:06 -080082 private final Map<DeviceId, Client> clientMap = new ConcurrentHashMap<>();
Andrea Campanella945ded22016-01-07 13:17:43 -080083
84 @Activate
Andrea Campanellace279ee2016-01-25 10:21:45 -080085 public void activate() {
Andrea Campanella945ded22016-01-07 13:17:43 -080086 log.info("Started");
87 }
88
89 @Deactivate
90 public void deactivate() {
Andrea Campanellac6ecc632016-03-10 17:57:06 -080091 clientMap.clear();
Andrea Campanella945ded22016-01-07 13:17:43 -080092 deviceMap.clear();
93 log.info("Stopped");
94 }
95
96 @Override
97 public Map<DeviceId, RestSBDevice> getDevices() {
Andrea Campanella2947e622016-01-27 09:23:46 -080098 return ImmutableMap.copyOf(deviceMap);
Andrea Campanella945ded22016-01-07 13:17:43 -080099 }
100
101 @Override
102 public RestSBDevice getDevice(DeviceId deviceInfo) {
103 return deviceMap.get(deviceInfo);
104 }
105
106 @Override
107 public RestSBDevice getDevice(IpAddress ip, int port) {
Andrea Campanella2947e622016-01-27 09:23:46 -0800108 return deviceMap.values().stream().filter(v -> v.ip().equals(ip)
109 && v.port() == port).findFirst().get();
Andrea Campanella945ded22016-01-07 13:17:43 -0800110 }
111
112 @Override
113 public void addDevice(RestSBDevice device) {
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800114 if (!deviceMap.containsKey(device.deviceId())) {
115 Client client = ignoreSslClient();
116 if (device.username() != null) {
117 String username = device.username();
118 String password = device.password() == null ? "" : device.password();
119 authenticate(client, username, password);
120 }
121 clientMap.put(device.deviceId(), client);
122 deviceMap.put(device.deviceId(), device);
123 } else {
124 log.warn("Trying to add a device that is already existing {}", device.deviceId());
125 }
126
Andrea Campanella945ded22016-01-07 13:17:43 -0800127 }
128
129 @Override
Andrea Campanella86294db2016-03-07 11:42:49 -0800130 public void removeDevice(DeviceId deviceId) {
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800131 clientMap.remove(deviceId);
Andrea Campanella86294db2016-03-07 11:42:49 -0800132 deviceMap.remove(deviceId);
Andrea Campanella945ded22016-01-07 13:17:43 -0800133 }
134
135 @Override
136 public boolean post(DeviceId device, String request, InputStream payload, String mediaType) {
Andrea Campanellac3851262016-06-08 12:27:11 +0200137 Response response = getResponse(device, request, payload, mediaType);
138 return checkReply(response);
139 }
140
141 @Override
142 public <T> T post(DeviceId device, String request, InputStream payload,
143 String mediaType, Class<T> responseClass) {
144 Response response = getResponse(device, request, payload, mediaType);
145 if (response.hasEntity()) {
146 return response.readEntity(responseClass);
147 }
148 log.error("Response from device {} for request {} contains no entity", device, request);
149 return null;
150 }
151
152 private Response getResponse(DeviceId device, String request, InputStream payload, String mediaType) {
Jian Li9d616492016-03-09 10:52:49 -0800153 WebTarget wt = getWebTarget(device, request);
Andrea Campanella945ded22016-01-07 13:17:43 -0800154
Jian Li9d616492016-03-09 10:52:49 -0800155 Response response = null;
Andrea Campanella945ded22016-01-07 13:17:43 -0800156 if (payload != null) {
157 try {
Jian Li9d616492016-03-09 10:52:49 -0800158 response = wt.request(mediaType)
159 .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
Andrea Campanella945ded22016-01-07 13:17:43 -0800160 } catch (IOException e) {
161 log.error("Cannot do POST {} request on device {} because can't read payload",
162 request, device);
163 }
164 } else {
Jian Li9d616492016-03-09 10:52:49 -0800165 response = wt.request(mediaType).post(Entity.entity(null, mediaType));
Andrea Campanella945ded22016-01-07 13:17:43 -0800166 }
Andrea Campanellac3851262016-06-08 12:27:11 +0200167 return response;
Andrea Campanella945ded22016-01-07 13:17:43 -0800168 }
169
170 @Override
171 public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {
Andrea Campanellad8d92db2016-01-14 16:24:41 -0800172
Jian Li9d616492016-03-09 10:52:49 -0800173 WebTarget wt = getWebTarget(device, request);
174 Response response = null;
Andrea Campanella945ded22016-01-07 13:17:43 -0800175 if (payload != null) {
176 try {
Jian Li9d616492016-03-09 10:52:49 -0800177 response = wt.request(mediaType)
178 .put(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
Andrea Campanella945ded22016-01-07 13:17:43 -0800179 } catch (IOException e) {
180 log.error("Cannot do PUT {} request on device {} because can't read payload",
181 request, device);
182 }
183 } else {
Jian Li9d616492016-03-09 10:52:49 -0800184 response = wt.request(mediaType).put(Entity.entity(null, mediaType));
Andrea Campanella945ded22016-01-07 13:17:43 -0800185 }
186 return checkReply(response);
187 }
188
189 @Override
190 public InputStream get(DeviceId device, String request, String mediaType) {
Jian Li9d616492016-03-09 10:52:49 -0800191 WebTarget wt = getWebTarget(device, request);
Andrea Campanella945ded22016-01-07 13:17:43 -0800192 String type;
193 switch (mediaType) {
194 case XML:
195 type = MediaType.APPLICATION_XML;
196 break;
197 case JSON:
198 type = MediaType.APPLICATION_JSON;
199 break;
200 default:
201 throw new IllegalArgumentException("Unsupported media type " + mediaType);
202
203 }
Andrea Campanella2947e622016-01-27 09:23:46 -0800204
Jian Li9d616492016-03-09 10:52:49 -0800205 Response s = wt.request(type).get();
Andrea Campanella2947e622016-01-27 09:23:46 -0800206 if (checkReply(s)) {
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800207 return new ByteArrayInputStream(wt.request(type)
Andrea Campanellac3851262016-06-08 12:27:11 +0200208 .get(String.class).getBytes(StandardCharsets.UTF_8));
Andrea Campanella2947e622016-01-27 09:23:46 -0800209 }
210 return null;
Andrea Campanella945ded22016-01-07 13:17:43 -0800211 }
212
213 @Override
Andrea Campanellace279ee2016-01-25 10:21:45 -0800214 public boolean patch(DeviceId device, String request, InputStream payload, String mediaType) {
Andrea Campanellace279ee2016-01-25 10:21:45 -0800215 try {
Andrea Campanella2947e622016-01-27 09:23:46 -0800216 log.debug("Url request {} ", getUrlString(device, request));
217 HttpPatch httprequest = new HttpPatch(getUrlString(device, request));
Andrea Campanella784ee0f2016-02-17 15:50:59 -0800218 if (deviceMap.get(device).username() != null) {
219 String pwd = deviceMap.get(device).password() == null ? "" : COLON + deviceMap.get(device).password();
220 String userPassword = deviceMap.get(device).username() + pwd;
Andrea Campanella2947e622016-01-27 09:23:46 -0800221 String base64string = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8));
222 httprequest.addHeader(AUTHORIZATION_PROPERTY, BASIC_AUTH_PREFIX + base64string);
223 }
Andrea Campanellace279ee2016-01-25 10:21:45 -0800224 if (payload != null) {
225 StringEntity input = new StringEntity(IOUtils.toString(payload, StandardCharsets.UTF_8));
226 input.setContentType(mediaType);
227 httprequest.setEntity(input);
228 }
Andrea Campanella2947e622016-01-27 09:23:46 -0800229 CloseableHttpClient httpClient;
230 if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
231 httpClient = getApacheSslBypassClient();
232 } else {
233 httpClient = HttpClients.createDefault();
234 }
235 int responseStatusCode = httpClient
236 .execute(httprequest)
Andrea Campanellace279ee2016-01-25 10:21:45 -0800237 .getStatusLine()
238 .getStatusCode();
239 return checkStatusCode(responseStatusCode);
Andrea Campanella2947e622016-01-27 09:23:46 -0800240 } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
241 log.error("Cannot do PATCH {} request on device {}",
242 request, device, e);
Andrea Campanellace279ee2016-01-25 10:21:45 -0800243 }
244 return false;
245 }
246
247 @Override
Andrea Campanella945ded22016-01-07 13:17:43 -0800248 public boolean delete(DeviceId device, String request, InputStream payload, String mediaType) {
Jian Li9d616492016-03-09 10:52:49 -0800249 WebTarget wt = getWebTarget(device, request);
250
251 // FIXME: do we need to delete an entry by enclosing data in DELETE request?
252 // wouldn't it be nice to use PUT to implement the similar concept?
253 Response response = wt.request(mediaType).delete();
254
Andrea Campanella945ded22016-01-07 13:17:43 -0800255 return checkReply(response);
256 }
257
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800258 private void authenticate(Client client, String username, String password) {
259 client.register(HttpAuthenticationFeature.basic(username, password));
260 }
261
Jian Li9d616492016-03-09 10:52:49 -0800262 private WebTarget getWebTarget(DeviceId device, String request) {
Andrea Campanella2947e622016-01-27 09:23:46 -0800263 log.debug("Sending request to URL {} ", getUrlString(device, request));
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800264 return clientMap.get(device).target(getUrlString(device, request));
Andrea Campanella2947e622016-01-27 09:23:46 -0800265 }
266
267 //FIXME security issue: this trusts every SSL certificate, even if is self-signed. Also deprecated methods.
268 private CloseableHttpClient getApacheSslBypassClient() throws NoSuchAlgorithmException,
269 KeyManagementException, KeyStoreException {
270 return HttpClients.custom().
271 setHostnameVerifier(new AllowAllHostnameVerifier()).
272 setSslcontext(new SSLContextBuilder()
273 .loadTrustMaterial(null, (arg0, arg1) -> true)
274 .build()).build();
275 }
276
277 private String getUrlString(DeviceId device, String request) {
278 if (deviceMap.get(device).url() != null) {
279 return deviceMap.get(device).protocol() + COLON + DOUBLESLASH
280 + deviceMap.get(device).url() + request;
281 } else {
282 return deviceMap.get(device).protocol() + COLON +
283 DOUBLESLASH +
284 deviceMap.get(device).ip().toString() +
285 COLON + deviceMap.get(device).port() + request;
286 }
Andrea Campanella945ded22016-01-07 13:17:43 -0800287 }
288
Jian Li9d616492016-03-09 10:52:49 -0800289 private boolean checkReply(Response response) {
Andrea Campanella945ded22016-01-07 13:17:43 -0800290 if (response != null) {
Andrea Campanellace279ee2016-01-25 10:21:45 -0800291 return checkStatusCode(response.getStatus());
Andrea Campanella945ded22016-01-07 13:17:43 -0800292 }
293 log.error("Null reply from device");
294 return false;
295 }
Andrea Campanellace279ee2016-01-25 10:21:45 -0800296
297 private boolean checkStatusCode(int statusCode) {
298 if (statusCode == STATUS_OK ||
299 statusCode == STATUS_CREATED ||
300 statusCode == STATUS_ACCEPTED) {
301 return true;
302 } else {
Andrea Campanella2947e622016-01-27 09:23:46 -0800303 log.error("Failed request, HTTP error code : "
Andrea Campanellace279ee2016-01-25 10:21:45 -0800304 + statusCode);
305 return false;
306 }
307 }
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800308
309 private Client ignoreSslClient() {
310 SSLContext sslcontext = null;
311
312 try {
313 sslcontext = SSLContext.getInstance("TLS");
314 sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
315 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
316 }
317
318 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
319 }
320
321 public X509Certificate[] getAcceptedIssuers() {
322 return new X509Certificate[0];
323 }
Andrea Campanellac6ecc632016-03-10 17:57:06 -0800324 } }, new java.security.SecureRandom());
325 } catch (NoSuchAlgorithmException | KeyManagementException e) {
326 e.printStackTrace();
327 }
328
329 return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
330 }
Andrea Campanella945ded22016-01-07 13:17:43 -0800331}