org.onosproject.protocol.rest.ctl.RestSBControllerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.protocol.rest.ctl.RestSBControllerImpl.java

Source

/*
 * 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.rest.ctl;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
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.rest.RestSBController;
import org.onosproject.protocol.rest.RestSBDevice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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 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;

/**
 * The implementation of RestSBController.
 */
@Component(immediate = true)
@Service
public class RestSBControllerImpl implements RestSBController {

    private static final Logger log = LoggerFactory.getLogger(RestSBControllerImpl.class);
    private static final String XML = "xml";
    private static final String JSON = "json";
    private static final String DOUBLESLASH = "//";
    private 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<>();

    @Activate
    public void activate() {
        log.info("Started");
    }

    @Deactivate
    public void deactivate() {
        clientMap.clear();
        deviceMap.clear();
        log.info("Stopped");
    }

    @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) {
        WebTarget wt = getWebTarget(device, request);

        Response response = null;
        if (payload != null) {
            try {
                response = wt.request(mediaType)
                        .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
            } catch (IOException e) {
                log.error("Cannot do POST {} request on device {} because can't read payload", request, device);
            }
        } else {
            response = wt.request(mediaType).post(Entity.entity(null, mediaType));
        }
        return checkReply(response);
    }

    @Override
    public boolean put(DeviceId device, String request, InputStream payload, String mediaType) {

        WebTarget wt = getWebTarget(device, request);
        Response response = null;
        if (payload != null) {
            try {
                response = wt.request(mediaType)
                        .put(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
            } catch (IOException e) {
                log.error("Cannot do PUT {} request on device {} because can't read payload", request, device);
            }
        } else {
            response = wt.request(mediaType).put(Entity.entity(null, mediaType));
        }
        return checkReply(response);
    }

    @Override
    public InputStream get(DeviceId device, String request, String mediaType) {
        WebTarget wt = getWebTarget(device, request);
        String type;
        switch (mediaType) {
        case XML:
            type = MediaType.APPLICATION_XML;
            break;
        case JSON:
            type = MediaType.APPLICATION_JSON;
            break;
        default:
            throw new IllegalArgumentException("Unsupported media type " + mediaType);

        }

        Response s = wt.request(type).get();
        if (checkReply(s)) {
            return new ByteArrayInputStream(wt.request(type).get(String.class).getBytes(StandardCharsets.UTF_8));
        }
        return null;
    }

    @Override
    public boolean patch(DeviceId device, String request, InputStream payload, String 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);
                httprequest.setEntity(input);
            }
            CloseableHttpClient httpClient;
            if (deviceMap.containsKey(device) && deviceMap.get(device).protocol().equals(HTTPS)) {
                httpClient = getApacheSslBypassClient();
            } else {
                httpClient = HttpClients.createDefault();
            }
            int responseStatusCode = httpClient.execute(httprequest).getStatusLine().getStatusCode();
            return checkStatusCode(responseStatusCode);
        } catch (IOException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            log.error("Cannot do PATCH {} request on device {}", request, device, e);
        }
        return false;
    }

    @Override
    public boolean delete(DeviceId device, String request, InputStream payload, String 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).delete();

        return checkReply(response);
    }

    private void authenticate(Client client, String username, String password) {
        client.register(HttpAuthenticationFeature.basic(username, password));
    }

    private 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();
    }

    private 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) {
            return checkStatusCode(response.getStatus());
        }
        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();
    }
}