Dépôt officiel du code source de l'ERP OpenConcerto
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());
}
}