OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.core.edm;

import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.erp.generationDoc.DocumentLocalStorageManager;
import org.openconcerto.erp.storage.StorageEngine;
import org.openconcerto.erp.storage.StorageEngines;
import org.openconcerto.sql.model.SQLInsert;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.utils.Base64;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.sync.SyncClient;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

public class AttachmentUtils {

    String generateBase64Key() throws NoSuchAlgorithmException {
        final KeyGenerator generator = KeyGenerator.getInstance("AES");
        generator.init(16 * 8);
        final SecretKey k = generator.generateKey();
        return Base64.encodeBytes(k.getEncoded(), Base64.DONT_BREAK_LINES);
    }

    SecretKey getSecretKey(String base64key) {
        byte[] b = Base64.decode(base64key.getBytes(StandardCharsets.UTF_8));
        if (b.length != 16) {
            throw new IllegalStateException("key length must be 16 bytes for AES");
        }
        return new SecretKeySpec(b, "AES");
    }

    public byte[] encrypt(SecretKey secretKey, byte[] in)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        return process(secretKey, in, Cipher.ENCRYPT_MODE);
    }

    public byte[] decrypt(SecretKey secretKey, byte[] in)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        return process(secretKey, in, Cipher.DECRYPT_MODE);
    }

    public byte[] process(SecretKey secretKey, byte[] in, int mode)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        final byte[] nonce = new byte[12];
        final byte[] e = secretKey.getEncoded();
        for (int i = 0; i < nonce.length; i++) {
            nonce[i] = (byte) (e[i] * e[i + 1] + e[i + 2]);
        }
        GCMParameterSpec spec = new GCMParameterSpec(16 * 8, nonce);
        cipher.init(mode, secretKey, spec);
        return cipher.doFinal(in);
    }

    public void uploadFile(File inFile, SQLRowAccessor rowSource, int idParent) throws SQLException {
        uploadFile(inFile, rowSource, idParent, null);
    }

    public void uploadFile(File inFile, SQLRowAccessor rowSource, int idParent, String nameInGed) throws SQLException {

        String encodeKey = fetchEncodedKey(rowSource.getTable().getTable("FWK_SCHEMA_METADATA"));
        // Création de la row attachment
        SQLRowValues rowValsAttachment = new SQLRowValues(rowSource.getTable().getTable("ATTACHMENT"));
        rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
        rowValsAttachment.put("SOURCE_ID", rowSource.getID());
        rowValsAttachment.put("ID_PARENT", idParent);
        if (encodeKey != null) {
            rowValsAttachment.put("ENCRYPTED", true);
        }

        SQLRow rowAttachment = rowValsAttachment.insert();
        int id = rowAttachment.getID();

        final String folderId = String.valueOf((id / 1000) * 1000);
        String subDir = "EDM/" + folderId;
        String fileNameID = String.valueOf(id);
        String ext = "";

        int i = inFile.getName().lastIndexOf('.');
        if (i > 0) {
            ext = inFile.getName().substring(i + 1);
        }

        final String fileWithIDNAme = fileNameID + "." + ext;

        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        boolean isOnCloud = config.isOnCloud();

        try {
            if (isOnCloud) {

                String remotePath = subDir;
                final List<StorageEngine> engines = StorageEngines.getInstance().getActiveEngines();
                File encodedFile = inFile;
                if (encodeKey != null) {
                    encodedFile = File.createTempFile("encrypted", inFile.getName());
                    final byte[] enc = encrypt(getSecretKey(encodeKey), FileUtils.readBytes(inFile));
                    Files.write(encodedFile.toPath(), enc);
                }

                for (StorageEngine storageEngine : engines) {
                    if (storageEngine.isConfigured() && storageEngine.allowAutoStorage()) {
                        final String path = remotePath;

                        try (FileInputStream in = new FileInputStream(encodedFile)) {
                            storageEngine.connect();
                            final BufferedInputStream inStream = new BufferedInputStream(in);
                            storageEngine.store(inStream, path, fileWithIDNAme, true);
                            inStream.close();
                            storageEngine.disconnect();
                        }

                    }
                }
                if (encodeKey != null) {
                    encodedFile.delete();
                }

            } else {
                // Upload File

                // Get file out
                File dirRoot = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(AttachmentSQLElement.DIRECTORY_PREFS);
                File storagePathFile = new File(dirRoot, subDir);
                storagePathFile.mkdirs();
                // TODO CHECK IF FILE EXISTS
                if (encodeKey == null) {
                    FileUtils.copyFile(inFile, new File(storagePathFile, fileWithIDNAme));
                } else {
                    final File encodedFile = new File(storagePathFile, fileWithIDNAme);
                    final byte[] enc = encrypt(getSecretKey(encodeKey), FileUtils.readBytes(inFile));
                    Files.write(encodedFile.toPath(), enc);
                }
            }

            // Update rowAttachment
            rowValsAttachment = rowAttachment.createEmptyUpdateRow();

            // Default is without extension
            String fileName = inFile.getName();
            if (nameInGed != null) {
                rowValsAttachment.put("NAME", nameInGed);
            } else {
                String name = fileName;
                int index = name.lastIndexOf('.');
                if (index > 0) {
                    name = name.substring(0, index);
                }
                rowValsAttachment.put("NAME", name);
            }

            rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
            rowValsAttachment.put("SOURCE_ID", rowSource.getID());
            rowValsAttachment.put("ID_PARENT", idParent);
            final String mimeType = Files.probeContentType(inFile.toPath());
            rowValsAttachment.put("MIMETYPE", mimeType != null ? mimeType : "application/octet-stream");
            rowValsAttachment.put("FILENAME", fileName);
            rowValsAttachment.put("FILESIZE", inFile.length());
            rowValsAttachment.put("STORAGE_PATH", subDir);
            rowValsAttachment.put("STORAGE_FILENAME", fileWithIDNAme);
            rowValsAttachment.put("ENCRYPTED", (encodeKey != null));
            // TODO THUMBNAIL
            // rowVals.put("THUMBNAIL", );
            // rowVals.put("THUMBNAIL_WIDTH", );
            // rowVals.put("THUMBNAIL_HEIGHT", );

            // needed for update count

            rowValsAttachment.commit();
            final Attachment a = new Attachment(rowValsAttachment);
            updateAttachmentsCountFromAttachment(a);

        } catch (Exception e) {
            if (rowAttachment != null) {
                config.getDirectory().getElement(AttachmentSQLElement.class).archive(rowAttachment.getID());
            }
            ExceptionHandler.handle("Impossible de sauvegarder le fichier " + inFile.getAbsolutePath(), e);
        }

    }

    public String fetchEncodedKey(SQLTable table) {
        final SQLSelect select = new SQLSelect();
        select.addSelect(table.getField("VALUE"));
        select.setWhere(new Where(table.getField("NAME"), "=", AttachmentSQLElement.EDM_KEY_METADATA));
        final List<?> rows = table.getDBSystemRoot().getDataSource().executeCol(select.asString());
        if (rows.size() == 1) {
            return rows.get(0).toString();
        }
        return null;
    }

    public void createKeyOnDatabase(SQLTable table) throws NoSuchAlgorithmException {
        final String s = fetchEncodedKey(table);
        if (s != null) {
            throw new IllegalStateException("key alread exists");
        }

        final SQLInsert insert = new SQLInsert();
        insert.add(table.getField("NAME"), AttachmentSQLElement.EDM_KEY_METADATA);
        insert.add(table.getField("VALUE"), generateBase64Key());
        table.getDBSystemRoot().getDataSource().execute(insert.asString());
    }

    public void createURL(String url, SQLRowAccessor rowSource, int idParent) throws SQLException {
        final SQLRowValues rowValsAttachment = new SQLRowValues(rowSource.getTable().getTable("ATTACHMENT"));
        rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
        rowValsAttachment.put("SOURCE_ID", rowSource.getID());
        rowValsAttachment.put("ID_PARENT", idParent);
        rowValsAttachment.put("NAME", url);
        rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
        rowValsAttachment.put("SOURCE_ID", rowSource.getID());
        rowValsAttachment.put("MIMETYPE", Attachment.MIMETYPE_URL);
        rowValsAttachment.put("FILENAME", url);
        rowValsAttachment.put("FILESIZE", 0);
        rowValsAttachment.put("STORAGE_PATH", "");
        rowValsAttachment.put("STORAGE_FILENAME", "");
        rowValsAttachment.commit();
    }

    public File getFile(Attachment attachment) {

        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        boolean isOnCloud = config.isOnCloud();

        String subDir = attachment.getStoragePath();
        String fileName = attachment.getStorageFileName();

        String remotePath = config.getSocieteID() + File.separator + subDir;

        File fTemp;
        try {
            fTemp = File.createTempFile("edm_", "oc");
        } catch (IOException e) {
            ExceptionHandler.handle("Impossible de créer le fichier temporaire de réception", e);
            return null;
        }
        File f = new File(fTemp.getParent(), fTemp.getName() + "-dir");
        f.mkdirs();
        fTemp.delete();

        if (isOnCloud) {
            remotePath = remotePath.replace('\\', '/');
            final SyncClient client = new SyncClient("https://" + config.getStorageServer());

            client.setVerifyHost(false);

            try {
                client.retrieveFile(f, remotePath, fileName, config.getToken());
            } catch (Exception e) {
                ExceptionHandler.handle("Impossible de récupérer le fichier depuis le cloud", e);
                return null;
            }

        } else {

            // Get file out
            File dirRoot = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(AttachmentSQLElement.DIRECTORY_PREFS);
            File storagePathFile = new File(dirRoot, subDir);
            File fileIn;
            try {
                fileIn = new File(storagePathFile, fileName).getCanonicalFile();
                if (fileIn.exists()) {
                    final File outFile = new File(f, fileName);
                    try {
                        FileUtils.copyFile(fileIn, outFile);
                    } catch (IOException e) {
                        ExceptionHandler.handle("Impossible de copier le fichier vers le fichier temporaire de réception", e);
                        return null;
                    }
                } else {
                    if (SwingUtilities.isEventDispatchThread()) {
                        JOptionPane.showMessageDialog(null, "Le fichier n'existe pas sur le serveur!\n" + fileIn.getAbsolutePath(), "Erreur fichier", JOptionPane.ERROR_MESSAGE);
                    } else {
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                JOptionPane.showMessageDialog(null, "Le fichier n'existe pas sur le serveur!\n" + fileIn.getAbsolutePath(), "Erreur fichier", JOptionPane.ERROR_MESSAGE);
                            }
                        });
                    }
                    return null;
                }
            } catch (IOException e1) {
                ExceptionHandler.handle("Impossible de trouver le fichier\n" + storagePathFile + File.pathSeparator + fileName, e1);
                return null;
            }
        }
        final File outFile = new File(f, fileName);
        if (attachment.isEncrypted() && outFile.length() > 0) {
            try {
                final byte[] bytes = FileUtils.readBytes(outFile);
                final String encodeKey = fetchEncodedKey(config.getRootSociete().getTable("FWK_SCHEMA_METADATA"));
                if (encodeKey == null) {
                    throw new IllegalStateException("missing key");
                }
                final byte[] decrypted = decrypt(getSecretKey(encodeKey), bytes);
                Files.write(outFile.toPath(), decrypted);
            } catch (Exception e) {
                ExceptionHandler.handle("Impossible de decrypter le fichier\n" + outFile.getAbsolutePath(), e);
                return null;
            }

        }
        outFile.setReadOnly();
        return outFile;

    }

    public void deleteFile(Attachment rowAttachment) throws SQLException, IllegalStateException {
        // Delete Row
        // Remove from DB first
        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        config.getDirectory().getElement(AttachmentSQLElement.class).archive(rowAttachment.getId());
        updateAttachmentsCountFromAttachment(rowAttachment);

        if (!rowAttachment.isFolder()) {
            boolean isOnCloud = config.isOnCloud();
            // Delete File
            String subDir = rowAttachment.getStoragePath();
            String fileName = rowAttachment.getStorageFileName();

            String remotePath = config.getSocieteID() + File.separator + subDir;
            if (isOnCloud) {
                remotePath = remotePath.replace('\\', '/');
                // final SyncClient client = new SyncClient("https://" + config.getStorageServer());
                //
                // client.setVerifyHost(false);

                // TODO DELETE FILE ON CLOUD OR RENAME?
                // client.retrieveFile(f, remotePath, fileName, config.getToken());
            } else {

                File dirRoot = DocumentLocalStorageManager.getInstance().getDocumentOutputDirectory(AttachmentSQLElement.DIRECTORY_PREFS);
                File storagePathFile = new File(dirRoot, subDir);
                File f = new File(storagePathFile, fileName);
                if (f.exists()) {
                    if (!f.delete()) {
                        throw new IllegalStateException("Une erreur est survenue lors de la suppression du fichier");
                    }
                }
            }
        }

    }

    private void updateAttachmentsCountFromAttachment(Attachment rowAttachment) {
        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        final SQLTable attachmentTable = config.getDirectory().getElement(AttachmentSQLElement.class).getTable();
        final SQLTable table = attachmentTable.getTable(rowAttachment.getSourceTable());
        final SQLRow source = table.getRow(rowAttachment.getSourceId());
        updateAttachmentsCountFromSource(source);
    }

    private void updateAttachmentsCountFromSource(SQLRow rowSource) {
        final SQLTable tableSource = rowSource.getTable();
        final SQLTable tableAtt = rowSource.getTable().getTable("ATTACHMENT");

        String req = "UPDATE " + tableSource.getSQLName().quote() + " SET " + tableSource.getField("ATTACHMENTS").getQuotedName() + "=(SELECT COUNT(*) FROM " + tableAtt.getSQLName().quote();
        req += " WHERE " + tableAtt.getArchiveField().getQuotedName() + "=0 AND " + tableAtt.getField("SOURCE_TABLE").getQuotedName() + "='" + tableSource.getName() + "'";
        req += " AND " + tableAtt.getField("SOURCE_ID").getQuotedName() + "=" + rowSource.getID() + ") WHERE " + tableSource.getKey().getQuotedName() + "=" + rowSource.getID();

        tableSource.getDBSystemRoot().getDataSource().execute(req);
    }

    public static void rename(Attachment rowAttachment, String newName) {
        rowAttachment.setName(newName);
        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        final SQLTable attachmentTable = config.getDirectory().getElement(AttachmentSQLElement.class).getTable();
        final String req = "UPDATE " + attachmentTable.getSQLName().quote() + " SET " + attachmentTable.getField("NAME").getQuotedName() + "=" + attachmentTable.getBase().quoteString(newName)
                + " WHERE " + attachmentTable.getKey().getQuotedName() + "=" + rowAttachment.getId();
        attachmentTable.getDBSystemRoot().getDataSource().execute(req);
    }

    public void createFolder(String folderName, SQLRowAccessor rowSource, int idParent) throws SQLException {
        final SQLRowValues rowValsAttachment = new SQLRowValues(rowSource.getTable().getTable("ATTACHMENT"));
        rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
        rowValsAttachment.put("SOURCE_ID", rowSource.getID());
        rowValsAttachment.put("ID_PARENT", idParent);
        rowValsAttachment.put("NAME", folderName);
        rowValsAttachment.put("SOURCE_TABLE", rowSource.getTable().getName());
        rowValsAttachment.put("SOURCE_ID", rowSource.getID());
        rowValsAttachment.put("MIMETYPE", Attachment.MIMETYPE_FOLDER);
        rowValsAttachment.put("FILENAME", "");
        rowValsAttachment.put("FILESIZE", 0);
        rowValsAttachment.put("STORAGE_PATH", "");
        rowValsAttachment.put("STORAGE_FILENAME", "");
        rowValsAttachment.commit();
    }

    public void move(Attachment a, Attachment folder) {
        if (!folder.isFolder()) {
            throw new IllegalArgumentException(folder + " is not a folder");
        }
        move(a, folder.getId());
    }

    public void move(Attachment a, int folderId) {
        final ComptaPropsConfiguration config = ComptaPropsConfiguration.getInstanceCompta();
        final SQLTable attachmentTable = config.getDirectory().getElement(AttachmentSQLElement.class).getTable();
        final String req = "UPDATE " + attachmentTable.getSQLName().quote() + " SET " + attachmentTable.getField("ID_PARENT").getQuotedName() + "=" + folderId + " WHERE "
                + attachmentTable.getKey().getQuotedName() + "=" + a.getId();
        attachmentTable.getDBSystemRoot().getDataSource().execute(req);

    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        System.err.println("AttachmentUtils.main() key : " + new AttachmentUtils().generateBase64Key());
    }
}