UpdateCheckTask.java

/*
 * Copyright (C) 2020-2024 by Savoir-faire Linux
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package net.jami.jams.server.update;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import net.jami.jams.common.serialization.adapters.GsonFactory;
import net.jami.jams.common.updater.FileDescription;
import net.jami.jams.common.utils.VersioningUtils;
import net.jami.jams.common.utils.X509Utils;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.TimerTask;

import javax.net.ssl.SSLContext;

@Slf4j
@Getter
@Setter
public class UpdateCheckTask extends TimerTask {

    private HashMap<String, FileDescription> remoteData = new HashMap<>();
    private HashMap<String, FileDescription> localData = new HashMap<>();
    private SSLContext sslContext;
    private volatile KeyStore trustStore;
    protected static volatile String UPDATE_SERVER_URI;

    private final Gson gson = GsonFactory.createGson();

    protected UpdateCheckTask() {
        try {
            InputStream is =
                    UpdateCheckTask.class.getClassLoader().getResourceAsStream("oem/ca.crt");
            X509Certificate certificate =
                    X509Utils.getCertificateFromPEMString(new String(is.readAllBytes()));
            trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            trustStore.setCertificateEntry("ca", certificate);

            is = UpdateCheckTask.class.getClassLoader().getResourceAsStream("oem/update.crt");
            certificate = X509Utils.getCertificateFromPEMString(new String(is.readAllBytes()));
            trustStore.setCertificateEntry("update", certificate);

            // Inject the SSL Connection here for a first time.
            sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, null).build();

            // read config json
            InputStream input =
                    this.getClass().getClassLoader().getResourceAsStream("oem/config.json");
            Reader reader = new InputStreamReader(input);
            JsonObject jsonObject = gson.fromJson(reader, JsonObject.class);
            UPDATE_SERVER_URI = jsonObject.get("UPDATE_URL").getAsString();
        } catch (Exception e) {
            log.error("Could not initialize the trust store with error {}", e.getMessage());
        }
    }

    @Override
    public void run() {
        try {
            // Get the local data
            localData = VersioningUtils.checkVersion(null);
            // Download the info from the remote server.
            getLatestVersion();
            localData.forEach(
                    (k, v) -> {
                        if (remoteData.containsKey(k) && remoteData.get(k).compareTo(v) > 0) {
                            log.info("Detected a new version on SFL servers!");
                            JAMSUpdater.updateAvailable.set(true);
                        }
                    });
        } catch (Exception e) {
            log.error("Could not check for updates with error: {}", e.getMessage());
        }
    }

    // This reads a file on the server which contains some basic info
    // about what new versions of WHAT are available.
    private void getLatestVersion() {
        try {
            // Step 1: Download a file called versions.json
            HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
            HttpResponse response =
                    httpClient.execute(new HttpGet(UPDATE_SERVER_URI + "/versions.json"));

            // Step 2: Load the file into the hashmaps
            byte[] bytes = response.getEntity().getContent().readAllBytes();
            JsonObject jsonObject = gson.fromJson(new String(bytes), JsonObject.class);

            jsonObject
                    .entrySet()
                    .forEach(
                            (entry) -> {
                                String key = entry.getKey();
                                JsonObject value = entry.getValue().getAsJsonObject();
                                String version = value.get("version").getAsString();
                                String filename = value.get("filename").getAsString();
                                String md5 = value.get("md5").getAsString();
                                remoteData.put(
                                        key, new FileDescription(filename, version, md5, key));
                            });
        } catch (Exception e) {
            log.warn("Could not establish connection to JAMS Update Center with error: " + e);
        }
    }
}