mirror of
https://github.com/sismics/docs.git
synced 2025-12-13 17:56:20 +00:00
Encrypt stored files in SHA 256
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
version="3.0" metadata-complete="true">
|
||||
|
||||
<!-- Override init parameter to avoid nasty file locking issue on windows. -->
|
||||
<servlet>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -26,12 +24,10 @@ import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.event.ExtractFileAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
@@ -147,29 +143,6 @@ public class AppResource extends BaseResource {
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from all files again.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/extract")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchExtract() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Raise an extract file content event
|
||||
AppContext.getInstance().getAsyncEventBus().post(new ExtractFileAsyncEvent());
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
*
|
||||
@@ -197,7 +170,7 @@ public class AppResource extends BaseResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
* Clean storage.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
@@ -233,38 +206,4 @@ public class AppResource extends BaseResource {
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate file variations.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/file_variations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchFileVariations() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get all files
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
|
||||
// Generate variations for each file
|
||||
for (File file : fileList) {
|
||||
java.io.File originalFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
try {
|
||||
FileUtil.saveVariations(file, originalFile);
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Error generating file variations", e);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -20,22 +23,28 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.EncryptionUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
@@ -77,20 +86,30 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
Document document;
|
||||
User user;
|
||||
try {
|
||||
document = documentDao.getDocument(documentId, principal.getId());
|
||||
user = userDao.getById(principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
|
||||
FileDao fileDao = new FileDao();
|
||||
// Keep unencrypted data in memory, because we will need it two times
|
||||
byte[] fileData;
|
||||
try {
|
||||
fileData = ByteStreams.toByteArray(fileBodyPart.getValueAs(InputStream.class));
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("StreamError", "Error reading the input file", e);
|
||||
}
|
||||
InputStream fileInputStream = new ByteArrayInputStream(fileData);
|
||||
|
||||
// Validate mime type
|
||||
InputStream is = new BufferedInputStream(fileBodyPart.getValueAs(InputStream.class));
|
||||
String mimeType;
|
||||
try {
|
||||
mimeType = MimeTypeUtil.guessMimeType(is);
|
||||
mimeType = MimeTypeUtil.guessMimeType(fileInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("ErrorGuessMime", "Error guessing mime type", e);
|
||||
}
|
||||
@@ -113,12 +132,13 @@ public class FileResource extends BaseResource {
|
||||
String fileId = fileDao.create(file);
|
||||
|
||||
// Save the file
|
||||
FileUtil.save(is, file);
|
||||
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
||||
|
||||
// Raise a new file created event
|
||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||
fileCreatedAsyncEvent.setDocument(document);
|
||||
fileCreatedAsyncEvent.setFile(file);
|
||||
fileCreatedAsyncEvent.setInputStream(fileInputStream);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
||||
|
||||
// Always return ok
|
||||
@@ -288,10 +308,12 @@ public class FileResource extends BaseResource {
|
||||
// Get the file
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
UserDao userDao = new UserDao();
|
||||
File file;
|
||||
Document document;
|
||||
try {
|
||||
file = fileDao.getFile(fileId);
|
||||
Document document = documentDao.getDocument(file.getDocumentId());
|
||||
document = documentDao.getDocument(file.getDocumentId());
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
@@ -304,12 +326,13 @@ public class FileResource extends BaseResource {
|
||||
|
||||
|
||||
// Get the stored file
|
||||
// TODO Decrypt file
|
||||
java.io.File storedfile;
|
||||
String mimeType;
|
||||
boolean decrypt = false;
|
||||
if (size != null) {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId + "_" + size).toFile();
|
||||
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
|
||||
decrypt = true; // Thumbnails are encrypted
|
||||
if (!storedfile.exists()) {
|
||||
storedfile = new java.io.File(getClass().getResource("/image/file.png").getFile());
|
||||
mimeType = MimeType.IMAGE_PNG;
|
||||
@@ -317,11 +340,35 @@ public class FileResource extends BaseResource {
|
||||
} else {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId).toFile();
|
||||
mimeType = file.getMimeType();
|
||||
decrypt = true; // Original files are encrypted
|
||||
}
|
||||
|
||||
// Stream the output and decrypt it if necessary
|
||||
StreamingOutput stream;
|
||||
User user = userDao.getById(document.getUserId());
|
||||
try {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
final InputStream responseInputStream = decrypt ?
|
||||
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
|
||||
|
||||
stream = new StreamingOutput() {
|
||||
@Override
|
||||
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
|
||||
try {
|
||||
ByteStreams.copy(responseInputStream, outputStream);
|
||||
} finally {
|
||||
responseInputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("FileError", "Error while reading the file", e);
|
||||
}
|
||||
|
||||
return Response.ok(storedfile)
|
||||
return Response.ok(stream)
|
||||
.header("Content-Type", mimeType)
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24 * 7))
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
version="3.0" metadata-complete="true">
|
||||
<display-name>Docs</display-name>
|
||||
|
||||
|
||||
<absolute-ordering></absolute-ordering>
|
||||
|
||||
<!-- This filter is used to secure URLs -->
|
||||
<filter>
|
||||
<filter-name>requestContextFilter</filter-name>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<img src="img/loader.gif" ng-if="!document && isEdit()" />
|
||||
<img src="img/loader.gif" ng-show="!document && isEdit()" />
|
||||
|
||||
<div ng-if="document || !isEdit()">
|
||||
<div ng-show="document || !isEdit()">
|
||||
<form class="form-horizontal" name="documentForm">
|
||||
<div class="control-group" ng-class="{ error: !documentForm.title.$valid }">
|
||||
<label class="control-label" for="inputTitle">Title</label>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<img src="img/loader.gif" ng-if="!document" />
|
||||
<img src="img/loader.gif" ng-show="!document" />
|
||||
|
||||
<div ng-if="document">
|
||||
<div ng-show="document">
|
||||
<div class="text-right">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger" ng-click="deleteDocument(document)"><span class="icon-trash icon-white"></span> Delete</button>
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small> <img ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" /></h1>
|
||||
<h1>{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small> <img ng-if="document" ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" /></h1>
|
||||
<p>
|
||||
<button class="btn btn-small btn-inverse" ng-click="share()"><span class="icon-share icon-white"></span> Share</button>
|
||||
<button class="btn btn-small" ng-repeat="share in document.shares" ng-click="showShare(share)"><span class="icon-ok"></span> {{ share.name ? share.name : 'shared' }}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<img src="img/loader.gif" ng-if="!user && isEdit()" />
|
||||
<img src="img/loader.gif" ng-show="!user && isEdit()" />
|
||||
|
||||
<div ng-if="user || !isEdit()">
|
||||
<div ng-show="user || !isEdit()">
|
||||
<h2 ng-show="isEdit()">Edit
|
||||
<small>"{{ user.username }}"</small>
|
||||
</h2>
|
||||
|
||||
@@ -43,13 +43,6 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
||||
Assert.assertEquals(0, json.getInt("document_count"));
|
||||
|
||||
// OCR-ize all files
|
||||
appResource = resource().path("/app/batch/extract");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
response = appResource.post(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
|
||||
// Rebuild Lucene index
|
||||
appResource = resource().path("/app/batch/reindex");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@@ -15,6 +16,8 @@ import org.junit.Test;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
@@ -88,6 +91,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
InputStream is = response.getEntityInputStream();
|
||||
byte[] fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(163510, fileBytes.length);
|
||||
|
||||
// Get the thumbnail data
|
||||
@@ -99,6 +103,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(41935, fileBytes.length);
|
||||
|
||||
// Get the web data
|
||||
@@ -110,45 +115,14 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(fileBytes));
|
||||
Assert.assertEquals(551084, fileBytes.length);
|
||||
|
||||
// Regenerate file variations
|
||||
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
|
||||
WebResource appResource = resource().path("/app/batch/file_variations");
|
||||
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
response = appResource.post(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Get the file data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
response = fileResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(163510, fileBytes.length);
|
||||
|
||||
// Get the thumbnail data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("size", "thumb");
|
||||
response = fileResource.queryParams(getParams).get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(41935, fileBytes.length);
|
||||
|
||||
// Get the web data
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
getParams = new MultivaluedMapImpl();
|
||||
getParams.putSingle("size", "web");
|
||||
response = fileResource.queryParams(getParams).get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
is = response.getEntityInputStream();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(551084, fileBytes.length);
|
||||
// Check that the files are not readable directly from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
InputStream storedFileInputStream = new BufferedInputStream(new FileInputStream(storedFile));
|
||||
Assert.assertNull(MimeTypeUtil.guessMimeType(storedFileInputStream));
|
||||
storedFileInputStream.close();
|
||||
|
||||
// Get all files from a document
|
||||
fileResource = resource().path("/file/list");
|
||||
@@ -195,7 +169,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check that files are deleted from FS
|
||||
java.io.File storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
||||
java.io.File thumbnailFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_thumb").toFile();
|
||||
Assert.assertFalse(storedFile.exists());
|
||||
|
||||
Reference in New Issue
Block a user