E2E overhaul

- Limit online editing/stream when e2e-encrypted file
- download files in subfolder to correct local folder name
- use httpd/unix-directory for folder in metadata
- set encryption status of subfolder

Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2020-04-02 14:50:27 +02:00
parent 4f6e810226
commit a2fc279c01
No known key found for this signature in database
GPG Key ID: 0E00D4D47D0C5AF7
55 changed files with 1403 additions and 426 deletions

View File

@ -126,6 +126,8 @@ services:
- su www-data -c "php /var/www/html/occ config:app:set circles --value 1 local_is_non_ssl"
- su www-data -c "php /var/www/html/occ config:system:set allow_local_remote_servers --value true --type bool"
- su www-data -c "php /var/www/html/occ circles:manage:create test public publicCircle"
- su www-data -c "git clone -b master https://github.com/nextcloud/end_to_end_encryption/ /var/www/html/apps/end_to_end_encryption/"
- su www-data -c "php /var/www/html/occ app:enable end_to_end_encryption"
- /usr/local/bin/run.sh
trigger:

View File

@ -397,6 +397,7 @@ dependencies {
implementation "com.github.stateless4j:stateless4j:2.6.0"
androidTestImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
androidTestImplementation "org.mockito:mockito-android:3.3.3"
androidTestImplementation 'net.bytebuddy:byte-buddy:1.10.5'
}
configurations.all {

View File

@ -1 +1 @@
372
372

View File

@ -31,7 +31,7 @@
<Match>
<Or>
<Class name="~.*BindingImpl"/>
<Class name="~.*\.DataBinderMapperImpl"/>
<Class name="~.*\.DataBinderMapperImpl" />
</Or>
</Match>
@ -53,6 +53,7 @@
<Bug pattern="PATH_TRAVERSAL_IN" />
<Bug pattern="ANDROID_EXTERNAL_FILE_ACCESS" />
<Bug pattern="BAS_BLOATED_ASSIGNMENT_SCOPE" />
<Bug pattern="IMC_IMMATURE_CLASS_BAD_SERIALVERSIONUID" />
<!-- This is unmanageable for now due to large amount of interconnected static state -->
<Bug pattern="FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY" />

View File

@ -0,0 +1,384 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2020 Tobias Kaminsky
* Copyright (C) 2020 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client;
import android.accounts.AccountManager;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.users.GetPrivateKeyOperation;
import com.owncloud.android.lib.resources.users.GetPublicKeyOperation;
import com.owncloud.android.lib.resources.users.SendCSROperation;
import com.owncloud.android.lib.resources.users.StorePrivateKeyOperation;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.utils.CsrHelper;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
import net.bytebuddy.utility.RandomString;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Random;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
public class EndToEndRandomIT extends AbstractIT {
public enum Action {
CREATE_FOLDER,
GO_INTO_FOLDER,
GO_UP,
UPLOAD_FILE
}
private static ArbitraryDataProvider arbitraryDataProvider;
private OCFile currentFolder;
private int actionCount = 20;
private String rootEncFolder = "/e/";
@BeforeClass
public static void initClass() {
arbitraryDataProvider = new ArbitraryDataProvider(targetContext.getContentResolver());
}
@Test
public void run() throws Exception {
init();
for (int i = 0; i < actionCount; i++) {
Action nextAction = Action.values()[new Random().nextInt(Action.values().length)];
switch (nextAction) {
case CREATE_FOLDER:
createFolder(i);
break;
case GO_INTO_FOLDER:
goIntoFolder(i);
break;
case GO_UP:
goUp(i);
break;
case UPLOAD_FILE:
uploadFile(i);
break;
default:
Log_OC.d(this, "[" + i + "/" + actionCount + "]" + " Unknown action: " + nextAction);
break;
}
}
}
@Test
public void uploadOneFile() throws Exception {
init();
uploadFile(0);
}
@Test
public void createFolder() throws Exception {
init();
currentFolder = createFolder(0);
assertNotNull(currentFolder);
}
@Test
public void createSubFolders() throws Exception {
init();
currentFolder = createFolder(0);
assertNotNull(currentFolder);
currentFolder = createFolder(1);
assertNotNull(currentFolder);
currentFolder = createFolder(2);
assertNotNull(currentFolder);
}
@Test
public void createSubFoldersWithFiles() throws Exception {
init();
currentFolder = createFolder(0);
assertNotNull(currentFolder);
uploadFile(1);
uploadFile(1);
uploadFile(2);
currentFolder = createFolder(1);
assertNotNull(currentFolder);
uploadFile(11);
uploadFile(12);
uploadFile(13);
currentFolder = createFolder(2);
assertNotNull(currentFolder);
uploadFile(21);
uploadFile(22);
uploadFile(23);
}
@Test
public void pseudoRandom() throws Exception {
init();
uploadFile(1);
createFolder(2);
goIntoFolder(3);
goUp(4);
createFolder(5);
uploadFile(6);
goUp(7);
goIntoFolder(8);
goIntoFolder(9);
uploadFile(10);
}
@Test
public void deleteFile() throws Exception {
init();
uploadFile(1);
deleteFile(1);
}
private void init() throws Exception {
// create folder
createFolder(rootEncFolder);
OCFile encFolder = createFolder(rootEncFolder + RandomString.make(5) + "/");
// encrypt it
assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
encFolder.getRemotePath(),
true)
.execute(client).isSuccess());
encFolder.setEncrypted(true);
getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
useExistingKeys();
rootEncFolder = encFolder.getDecryptedRemotePath();
currentFolder = encFolder;
}
private OCFile createFolder(int i) {
String path = currentFolder.getDecryptedRemotePath() + RandomString.make(5) + "/";
Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Create folder: " + path);
return createFolder(path);
}
private void goIntoFolder(int i) {
ArrayList<OCFile> folders = new ArrayList<>();
for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
if (file.isFolder()) {
folders.add(file);
}
}
if (folders.isEmpty()) {
Log_OC.d(this, "[" + i + "/" + actionCount + "] " + "Go into folder: No folders");
return;
}
currentFolder = folders.get(new Random().nextInt(folders.size()));
Log_OC.d(this,
"[" + i + "/" + actionCount + "] " + "Go into folder: " + currentFolder.getDecryptedRemotePath());
}
private void goUp(int i) {
if (currentFolder.getRemotePath().equals(rootEncFolder)) {
Log_OC.d(this,
"[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
return;
}
currentFolder = getStorageManager().getFileById(currentFolder.getParentId());
if (currentFolder == null) {
throw new RuntimeException("Current folder is null");
}
Log_OC.d(this,
"[" + i + "/" + actionCount + "] " + "Go up to folder: " + currentFolder.getDecryptedRemotePath());
}
private void uploadFile(int i) throws IOException {
String fileName = RandomString.make(5) + ".txt";
createFile(fileName, new Random().nextInt(50000));
String path = currentFolder.getRemotePath() + fileName;
Log_OC.d(this,
"[" + i + "/" + actionCount + "] " +
"Upload file to: " + currentFolder.getDecryptedRemotePath() + fileName);
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + File.separator + fileName,
path,
account.name);
uploadOCUpload(ocUpload);
}
private void deleteFile(int i) {
ArrayList<OCFile> files = new ArrayList<>();
for (OCFile file : getStorageManager().getFolderContent(currentFolder, false)) {
if (!file.isFolder()) {
files.add(file);
}
}
OCFile fileToDelete = files.get(new Random().nextInt(files.size()));
Log_OC.d(this,
"[" + i + "/" + actionCount + "] " +
"Delete file: " + currentFolder.getDecryptedRemotePath() + fileToDelete.getDecryptedFileName());
assertTrue(new RemoveFileOperation(fileToDelete,
false,
account,
false,
targetContext)
.execute(client, getStorageManager())
.isSuccess());
}
@Test
public void reInit() throws Exception {
// create folder
OCFile encFolder = createFolder(rootEncFolder);
// encrypt it
assertTrue(new ToggleEncryptionRemoteOperation(encFolder.getLocalId(),
encFolder.getRemotePath(),
true)
.execute(client).isSuccess());
encFolder.setEncrypted(true);
getStorageManager().saveFolder(encFolder, new ArrayList<>(), new ArrayList<>());
createKeys();
// delete keys
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PRIVATE_KEY);
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PUBLIC_KEY);
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.MNEMONIC);
useExistingKeys();
}
private void useExistingKeys() throws Exception {
// download them from server
GetPublicKeyOperation publicKeyOperation = new GetPublicKeyOperation();
RemoteOperationResult publicKeyResult = publicKeyOperation.execute(account, targetContext);
assertTrue(publicKeyResult.isSuccess());
String publicKeyFromServer = (String) publicKeyResult.getData().get(0);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
EncryptionUtils.PUBLIC_KEY,
publicKeyFromServer);
GetPrivateKeyOperation privateKeyOperation = new GetPrivateKeyOperation();
RemoteOperationResult privateKeyResult = privateKeyOperation.execute(account, targetContext);
assertTrue(privateKeyResult.isSuccess());
String privateKey = (String) privateKeyResult.getData().get(0);
String mnemonic = generateMnemonicString();
String decryptedPrivateKey = EncryptionUtils.decryptPrivateKey(privateKey, mnemonic);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
EncryptionUtils.PRIVATE_KEY, decryptedPrivateKey);
Log_OC.d(this, "Private key successfully decrypted and stored");
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC, mnemonic);
}
/*
TODO do not c&p code
*/
private void createKeys() throws Exception {
String publicKey;
// Create public/private key pair
KeyPair keyPair = EncryptionUtils.generateKeyPair();
// create CSR
AccountManager accountManager = AccountManager.get(targetContext);
String userId = accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID);
String urlEncoded = CsrHelper.generateCsrPemEncodedString(keyPair, userId);
SendCSROperation operation = new SendCSROperation(urlEncoded);
RemoteOperationResult result = operation.execute(account, targetContext);
if (result.isSuccess()) {
publicKey = (String) result.getData().get(0);
} else {
throw new Exception("failed to send CSR", result.getException());
}
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKey.getEncoded());
String privatePemKeyString = EncryptionUtils.privateKeyToPEM(privateKey);
String encryptedPrivateKey = EncryptionUtils.encryptPrivateKey(privatePemKeyString,
generateMnemonicString());
// upload encryptedPrivateKey
StorePrivateKeyOperation storePrivateKeyOperation = new StorePrivateKeyOperation(encryptedPrivateKey);
RemoteOperationResult storePrivateKeyResult = storePrivateKeyOperation.execute(account, targetContext);
if (storePrivateKeyResult.isSuccess()) {
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PRIVATE_KEY,
privateKeyString);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.PUBLIC_KEY, publicKey);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, EncryptionUtils.MNEMONIC,
generateMnemonicString());
} else {
throw new RuntimeException("Error uploading private key!");
}
}
private String generateMnemonicString() {
return "1 2 3 4 5 6";
}
}

View File

@ -163,11 +163,12 @@ public class FileDisplayActivityIT extends AbstractIT {
@Test
public void allFiles() {
// ActivityScenario<FileDisplayActivity> sut = ActivityScenario.launch(FileDisplayActivity.class);
FileDisplayActivity sut = activityRule.launchActivity(null);
// given test folder
assertTrue(new CreateFolderOperation("/test/", true).execute(client, getStorageManager()).isSuccess());
assertTrue(new CreateFolderOperation("/test/", account, targetContext)
.execute(client, getStorageManager())
.isSuccess());
// navigate into it
OCFile test = getStorageManager().getFileByPath("/test/");

View File

@ -10,27 +10,37 @@ import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import com.evernote.android.job.JobRequest;
import com.facebook.testing.screenshot.Screenshot;
import com.nextcloud.client.RetryTestRule;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.account.UserAccountManagerImpl;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientFactory;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.RemoveFileRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import junit.framework.TestCase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import java.io.File;
import java.io.FileWriter;
@ -56,7 +66,7 @@ import static org.junit.Assert.assertTrue;
//@RunWith(AndroidJUnit4.class)
public abstract class AbstractIT {
@Rule public RetryTestRule retryTestRule = new RetryTestRule();
// @Rule public RetryTestRule retryTestRule = new RetryTestRule();
protected static OwnCloudClient client;
protected static Account account;
@ -99,7 +109,7 @@ public abstract class AbstractIT {
waitForServer(client, baseUrl);
deleteAllFiles(); // makes sure that no file/folder is in root
// deleteAllFiles(); // makes sure that no file/folder is in root
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
@ -124,8 +134,18 @@ public abstract class AbstractIT {
RemoteFile remoteFile = (RemoteFile) object;
if (!remoteFile.getRemotePath().equals("/")) {
if (remoteFile.isEncrypted()) {
assertTrue(new ToggleEncryptionRemoteOperation(remoteFile.getLocalId(),
remoteFile.getRemotePath(),
false)
.execute(client)
.isSuccess());
}
assertTrue(new RemoveFileRemoteOperation(remoteFile.getRemotePath())
.execute(client).isSuccess());
.execute(client)
.isSuccess()
);
}
}
}
@ -147,7 +167,7 @@ public abstract class AbstractIT {
createFile("chunkedFile.txt", 500000);
}
private static void createFile(String name, int iteration) throws IOException {
public static void createFile(String name, int iteration) throws IOException {
File file = new File(FileStorageUtils.getSavePath(account.name) + File.separator + name);
file.createNewFile();
@ -235,4 +255,74 @@ public abstract class AbstractIT {
e.printStackTrace();
}
}
public OCFile createFolder(String remotePath) {
TestCase.assertTrue(new CreateFolderOperation(remotePath, account, targetContext)
.execute(client, getStorageManager())
.isSuccess());
return getStorageManager().getFileByDecryptedRemotePath(remotePath);
}
public void uploadOCUpload(OCUpload ocUpload) {
ConnectivityService connectivityServiceMock = new ConnectivityService() {
@Override
public boolean isInternetWalled() {
return false;
}
@Override
public boolean isOnlineWithWifi() {
return true;
}
@Override
public JobRequest.NetworkType getActiveNetworkType() {
return JobRequest.NetworkType.ANY;
}
};
PowerManagementService powerManagementServiceMock = new PowerManagementService() {
@Override
public boolean isPowerSavingEnabled() {
return false;
}
@Override
public boolean isPowerSavingExclusionAvailable() {
return false;
}
@Override
public boolean isBatteryCharging() {
return false;
}
};
UserAccountManager accountManager = UserAccountManagerImpl.fromContext(targetContext);
UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(accountManager,
targetContext.getContentResolver());
UploadFileOperation newUpload = new UploadFileOperation(
uploadsStorageManager,
connectivityServiceMock,
powerManagementServiceMock,
account,
null,
ocUpload,
FileUploader.NameCollisionPolicy.DEFAULT,
FileUploader.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false
);
newUpload.addRenameUploadListener(() -> {
// dummy
});
newUpload.setRemoteFolderToBeCreated();
RemoteOperationResult result = newUpload.execute(client, getStorageManager());
assertTrue(result.getLogMessage(), result.isSuccess());
}
}

View File

@ -1,5 +1,6 @@
package com.owncloud.android;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
@ -26,16 +27,17 @@ public class FileIT extends AbstractIT {
// folder does not exist yet
assertNull(getStorageManager().getFileByPath(path));
SyncOperation syncOp = new CreateFolderOperation(path, true);
SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
RemoteOperationResult result = syncOp.execute(client, getStorageManager());
assertTrue(result.toString(), result.isSuccess());
// folder exists
assertTrue(getStorageManager().getFileByPath(path).isFolder());
OCFile file = getStorageManager().getFileByPath(path);
assertTrue(file.isFolder());
// cleanup
new RemoveFileOperation(path, false, account, false, targetContext).execute(client, getStorageManager());
new RemoveFileOperation(file, false, account, false, targetContext).execute(client, getStorageManager());
}
@Test
@ -44,14 +46,20 @@ public class FileIT extends AbstractIT {
// folder does not exist yet
assertNull(getStorageManager().getFileByPath(path));
SyncOperation syncOp = new CreateFolderOperation(path, true);
SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
RemoteOperationResult result = syncOp.execute(client, getStorageManager());
assertTrue(result.toString(), result.isSuccess());
// folder exists
assertTrue(getStorageManager().getFileByPath(path).isFolder());
OCFile file = getStorageManager().getFileByPath(path);
assertTrue(file.isFolder());
// cleanup
new RemoveFileOperation("/testFolder/", false, account, false, targetContext).execute(client, getStorageManager());
new RemoveFileOperation(file,
false,
account,
false,
targetContext)
.execute(client, getStorageManager());
}
}

View File

@ -86,7 +86,7 @@ public class ScreenshotsIT extends AbstractIT {
// folder does not exist yet
if (getStorageManager().getFileByPath(path) == null) {
SyncOperation syncOp = new CreateFolderOperation(path, true);
SyncOperation syncOp = new CreateFolderOperation(path, account, targetContext);
RemoteOperationResult result = syncOp.execute(client, getStorageManager());
assertTrue(result.isSuccess());

View File

@ -30,11 +30,9 @@ import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.Connectivity;
import com.nextcloud.client.network.ConnectivityService;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import org.jetbrains.annotations.NotNull;
@ -44,8 +42,6 @@ import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static junit.framework.TestCase.assertTrue;
/**
* Tests related to file uploads
*/
@ -95,92 +91,66 @@ public class UploadIT extends AbstractIT {
@Test
public void testEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
"/testUpload/empty.txt", account.name);
"/testUpload/empty.txt",
account.name);
RemoteOperationResult result = testUpload(ocUpload);
assertTrue(result.toString(), result.isSuccess());
uploadOCUpload(ocUpload);
// cleanup
new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
new RemoveFileOperation(new OCFile("/testUpload/"),
false,
account,
false,
targetContext)
.execute(client, getStorageManager());
}
@Test
public void testNonEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/nonEmpty.txt",
"/testUpload/nonEmpty.txt", account.name);
"/testUpload/nonEmpty.txt",
account.name);
RemoteOperationResult result = testUpload(ocUpload);
assertTrue(result.toString(), result.isSuccess());
uploadOCUpload(ocUpload);
// cleanup
new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
new RemoveFileOperation(new OCFile("/testUpload/"),
false,
account,
false,
targetContext)
.execute(client, getStorageManager());
}
@Test
public void testChunkedUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/chunkedFile.txt",
"/testUpload/chunkedFile.txt", account.name);
"/testUpload/chunkedFile.txt", account.name);
RemoteOperationResult result = testUpload(ocUpload);
assertTrue(result.toString(), result.isSuccess());
uploadOCUpload(ocUpload);
// cleanup
new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
}
public RemoteOperationResult testUpload(OCUpload ocUpload) {
UploadFileOperation newUpload = new UploadFileOperation(
storageManager,
connectivityServiceMock,
powerManagementServiceMock,
account,
null,
ocUpload,
FileUploader.NameCollisionPolicy.DEFAULT,
FileUploader.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false
);
newUpload.addRenameUploadListener(() -> {
// dummy
});
newUpload.setRemoteFolderToBeCreated();
return newUpload.execute(client, getStorageManager());
new RemoveFileOperation(new OCFile("/testUpload/"),
false,
account,
false,
targetContext)
.execute(client, getStorageManager());
}
@Test
public void testUploadInNonExistingFolder() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getSavePath(account.name) + "/empty.txt",
"/testUpload/2/3/4/1.txt", account.name);
UploadFileOperation newUpload = new UploadFileOperation(
storageManager,
connectivityServiceMock,
powerManagementServiceMock,
account,
null,
ocUpload,
FileUploader.NameCollisionPolicy.DEFAULT,
FileUploader.LOCAL_BEHAVIOUR_COPY,
targetContext,
false,
false
);
newUpload.addRenameUploadListener(() -> {
// dummy
});
"/testUpload/2/3/4/1.txt", account.name);
newUpload.setRemoteFolderToBeCreated();
RemoteOperationResult result = newUpload.execute(client, getStorageManager());
assertTrue(result.toString(), result.isSuccess());
uploadOCUpload(ocUpload);
// cleanup
new RemoveFileOperation("/testUpload/", false, account, false, targetContext).execute(client, getStorageManager());
new RemoveFileOperation(new OCFile("/testUpload/"),
false,
account,
false,
targetContext)
.execute(client, getStorageManager());
}
}

View File

@ -44,7 +44,7 @@ public class FolderPickerActivityIT {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/file.test");
origin.setRemotePath("/remotePath/test");
origin.setEncryptedRemotePath("/remotePath/test");
// Act
targetActivity.setFile(origin);
@ -60,7 +60,7 @@ public class FolderPickerActivityIT {
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/");
origin.setFileId(1);
origin.setRemotePath("/test/");
origin.setEncryptedRemotePath("/test/");
origin.setStoragePath("/test/");
origin.setFolder();
@ -78,7 +78,7 @@ public class FolderPickerActivityIT {
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/");
origin.setFileId(1);
origin.setRemotePath("/");
origin.setEncryptedRemotePath("/");
origin.setStoragePath("/");
origin.setFolder();
@ -109,7 +109,7 @@ public class FolderPickerActivityIT {
// Arrange
FolderPickerActivity targetActivity = activityRule.getActivity();
OCFile origin = new OCFile("/test/file.test");
origin.setRemotePath("/test/file.test");
origin.setEncryptedRemotePath("/test/file.test");
OCFile target = new OCFile("/test/");

View File

@ -91,7 +91,7 @@ class OCFileListFragmentIT : AbstractIT() {
@Test
fun showRichWorkspace() {
assertTrue(CreateFolderOperation("/test/", true).execute(client, storageManager).isSuccess)
assertTrue(CreateFolderOperation("/test/", account, targetContext).execute(client, storageManager).isSuccess)
val ocUpload = OCUpload(FileStorageUtils.getSavePath(account.name) + "/nonEmpty.txt",
"/test/Readme.md",
@ -159,7 +159,9 @@ class OCFileListFragmentIT : AbstractIT() {
@Test
fun createAndShowShareToUser() {
val path = "/shareToAdmin/"
TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
.execute(client, storageManager)
.isSuccess)
// share folder to user "admin"
TestCase.assertTrue(CreateShareRemoteOperation(path,
@ -181,7 +183,9 @@ class OCFileListFragmentIT : AbstractIT() {
@Test
fun createAndShowShareToGroup() {
val path = "/shareToGroup/"
TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
.execute(client, storageManager)
.isSuccess)
// share folder to group
assertTrue(CreateShareRemoteOperation("/shareToGroup/",
@ -203,7 +207,9 @@ class OCFileListFragmentIT : AbstractIT() {
@Test
fun createAndShowShareToCircle() {
val path = "/shareToCircle/"
TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
.execute(client, storageManager)
.isSuccess)
// share folder to circle
// get circleId
@ -232,7 +238,9 @@ class OCFileListFragmentIT : AbstractIT() {
@Test
fun createAndShowShareViaLink() {
val path = "/shareViaLink/"
TestCase.assertTrue(CreateFolderOperation(path, true).execute(client, storageManager).isSuccess)
TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
.execute(client, storageManager)
.isSuccess)
// share folder via public link
TestCase.assertTrue(CreateShareRemoteOperation("/shareViaLink/",

View File

@ -22,15 +22,19 @@
package com.owncloud.android.util;
import android.os.Build;
import android.text.TextUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.CsrHelper;
import com.owncloud.android.utils.EncryptionUtils;
import net.bytebuddy.utility.RandomString;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -52,6 +56,26 @@ import androidx.annotation.RequiresApi;
import androidx.test.runner.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.owncloud.android.utils.EncryptionUtils.EncryptedFile;
import static com.owncloud.android.utils.EncryptionUtils.decodeStringToBase64Bytes;
import static com.owncloud.android.utils.EncryptionUtils.decryptFile;
import static com.owncloud.android.utils.EncryptionUtils.decryptFolderMetaData;
import static com.owncloud.android.utils.EncryptionUtils.decryptPrivateKey;
import static com.owncloud.android.utils.EncryptionUtils.decryptStringAsymmetric;
import static com.owncloud.android.utils.EncryptionUtils.decryptStringSymmetric;
import static com.owncloud.android.utils.EncryptionUtils.deserializeJSON;
import static com.owncloud.android.utils.EncryptionUtils.encodeBytesToBase64String;
import static com.owncloud.android.utils.EncryptionUtils.encryptFile;
import static com.owncloud.android.utils.EncryptionUtils.encryptFolderMetadata;
import static com.owncloud.android.utils.EncryptionUtils.generateKey;
import static com.owncloud.android.utils.EncryptionUtils.generateSHA512;
import static com.owncloud.android.utils.EncryptionUtils.getMD5Sum;
import static com.owncloud.android.utils.EncryptionUtils.ivDelimiter;
import static com.owncloud.android.utils.EncryptionUtils.ivLength;
import static com.owncloud.android.utils.EncryptionUtils.randomBytes;
import static com.owncloud.android.utils.EncryptionUtils.saltLength;
import static com.owncloud.android.utils.EncryptionUtils.serializeJSON;
import static com.owncloud.android.utils.EncryptionUtils.verifySHA512;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@ -110,25 +134,60 @@ public class EncryptionTestIT {
@Test
public void encryptStringAsymmetric() throws Exception {
byte[] key1 = EncryptionUtils.generateKey();
String base64encodedKey = EncryptionUtils.encodeBytesToBase64String(key1);
byte[] key1 = generateKey();
String base64encodedKey = encodeBytesToBase64String(key1);
String encryptedString = EncryptionUtils.encryptStringAsymmetric(base64encodedKey, cert);
String decryptedString = EncryptionUtils.decryptStringAsymmetric(encryptedString, privateKey);
String decryptedString = decryptStringAsymmetric(encryptedString, privateKey);
byte[] key2 = EncryptionUtils.decodeStringToBase64Bytes(decryptedString);
byte[] key2 = decodeStringToBase64Bytes(decryptedString);
assertTrue(Arrays.equals(key1, key2));
}
@Test
public void encryptStringSymmetricRandom() throws Exception {
int max = 500;
for (int i = 0; i < max; i++) {
Log_OC.d("EncryptionTestIT", i + " of " + max);
byte[] key = generateKey();
String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
String decryptedString = decryptStringSymmetric(encryptedString, key);
assertEquals(privateKey, decryptedString);
}
}
@Test
public void encryptStringSymmetric() throws Exception {
byte[] key = EncryptionUtils.generateKey();
int max = 5000;
byte[] key = generateKey();
String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
String decryptedString = EncryptionUtils.decryptStringSymmetric(encryptedString, key);
for (int i = 0; i < max; i++) {
Log_OC.d("EncryptionTestIT", i + " of " + max);
assertEquals(privateKey, decryptedString);
String encryptedString = EncryptionUtils.encryptStringSymmetric(privateKey, key);
int delimiterPosition = encryptedString.indexOf(ivDelimiter);
if (delimiterPosition == -1) {
throw new RuntimeException("IV not found!");
}
String ivString = encryptedString.substring(delimiterPosition + ivDelimiter.length());
if (TextUtils.isEmpty(ivString)) {
delimiterPosition = encryptedString.lastIndexOf(ivDelimiter);
ivString = encryptedString.substring(delimiterPosition + ivDelimiter.length());
if (TextUtils.isEmpty(ivString)) {
throw new RuntimeException("IV string is empty");
}
}
String decryptedString = decryptStringSymmetric(encryptedString, key);
assertEquals(privateKey, decryptedString);
}
}
@Test
@ -140,10 +199,10 @@ public class EncryptionTestIT {
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
byte[] privateKeyBytes = privateKey.getEncoded();
String privateKeyString = EncryptionUtils.encodeBytesToBase64String(privateKeyBytes);
String privateKeyString = encodeBytesToBase64String(privateKeyBytes);
String encryptedString = EncryptionUtils.encryptPrivateKey(privateKeyString, keyPhrase);
String decryptedString = EncryptionUtils.decryptPrivateKey(encryptedString, keyPhrase);
String decryptedString = decryptPrivateKey(encryptedString, keyPhrase);
assertEquals(privateKeyString, decryptedString);
}
@ -155,7 +214,7 @@ public class EncryptionTestIT {
KeyPair keyPair = keyGen.generateKeyPair();
assertFalse(CsrHelper.generateCsrPemEncodedString(keyPair, "").isEmpty());
assertFalse(EncryptionUtils.encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
assertFalse(encodeBytesToBase64String(keyPair.getPublic().getEncoded()).isEmpty());
}
/**
@ -167,31 +226,31 @@ public class EncryptionTestIT {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = EncryptionUtils.encryptFolderMetadata(
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1, privateKey);
// serialize
String encryptedJson = EncryptionUtils.serializeJSON(encryptedFolderMetadata1);
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
// de-serialize
EncryptedFolderMetadata encryptedFolderMetadata2 = EncryptionUtils.deserializeJSON(encryptedJson,
new TypeToken<EncryptedFolderMetadata>() {
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
new TypeToken<EncryptedFolderMetadata>() {
});
// decrypt
DecryptedFolderMetadata decryptedFolderMetadata2 = EncryptionUtils.decryptFolderMetaData(
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
encryptedFolderMetadata2, privateKey);
// compare
assertTrue(compareJsonStrings(EncryptionUtils.serializeJSON(decryptedFolderMetadata1),
EncryptionUtils.serializeJSON(decryptedFolderMetadata2)));
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
serializeJSON(decryptedFolderMetadata2)));
}
@Test
public void testCryptFileWithoutMetadata() throws Exception {
byte[] key = EncryptionUtils.decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
byte[] iv = EncryptionUtils.decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
byte[] authTag = EncryptionUtils.decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
byte[] key = decodeStringToBase64Bytes("WANM0gRv+DhaexIsI0T3Lg==");
byte[] iv = decodeStringToBase64Bytes("gKm3n+mJzeY26q4OfuZEqg==");
byte[] authTag = decodeStringToBase64Bytes("PboI9tqHHX3QeAA22PIu4w==");
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r", "78f42172166f9dc8fd1a7156b1753353", key, iv, authTag));
}
@ -202,25 +261,104 @@ public class EncryptionTestIT {
// n9WXAIXO2wRY4R8nXwmo
assertTrue(cryptFile("ia7OEEEyXMoRa1QWQk8r",
"78f42172166f9dc8fd1a7156b1753353",
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
"78f42172166f9dc8fd1a7156b1753353",
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
.getEncrypted().getKey()),
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
.getInitializationVector()),
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
decodeStringToBase64Bytes(metadata.getFiles().get("ia7OEEEyXMoRa1QWQk8r")
.getAuthenticationTag())));
// n9WXAIXO2wRY4R8nXwmo
assertTrue(cryptFile("n9WXAIXO2wRY4R8nXwmo",
"825143ed1f21ebb0c3b3c3f005b2f5db",
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
"825143ed1f21ebb0c3b3c3f005b2f5db",
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
.getEncrypted().getKey()),
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
.getInitializationVector()),
EncryptionUtils.decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
decodeStringToBase64Bytes(metadata.getFiles().get("n9WXAIXO2wRY4R8nXwmo")
.getAuthenticationTag())));
}
@Test
public void bigMetadata() throws Exception {
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
// encrypt
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
decryptedFolderMetadata1, privateKey);
// serialize
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
// de-serialize
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
new TypeToken<EncryptedFolderMetadata>() {
});
// decrypt
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
encryptedFolderMetadata2, privateKey);
// compare
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
serializeJSON(decryptedFolderMetadata2)));
// prefill with 500
for (int i = 0; i < 500; i++) {
addFile(decryptedFolderMetadata1, i);
}
int max = 505;
for (int i = 500; i < max; i++) {
Log_OC.d(this, "Big metadata: " + i + " of " + max);
addFile(decryptedFolderMetadata1, i);
// encrypt
encryptedFolderMetadata1 = encryptFolderMetadata(decryptedFolderMetadata1, privateKey);
// serialize
encryptedJson = serializeJSON(encryptedFolderMetadata1);
// de-serialize
encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
new TypeToken<EncryptedFolderMetadata>() {
});
// decrypt
decryptedFolderMetadata2 = decryptFolderMetaData(encryptedFolderMetadata2, privateKey);
// compare
assertTrue(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
serializeJSON(decryptedFolderMetadata2)));
assertEquals(i + 3, decryptedFolderMetadata1.getFiles().size());
assertEquals(i + 3, decryptedFolderMetadata2.getFiles().size());
}
}
private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
// Add new file
// Always generate new
byte[] key = generateKey();
byte[] iv = randomBytes(ivLength);
byte[] authTag = randomBytes((128 / 8));
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
data.setFilename(counter + ".txt");
data.setVersion(1);
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
file.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
file.setEncrypted(data);
file.setMetadataKey(0);
file.setAuthenticationTag(EncryptionUtils.encodeBytesToBase64String(authTag));
decryptedFolderMetadata.getFiles().put(RandomString.make(20), file);
}
/**
* generates new keys and tests if they are unique
*/
@ -229,7 +367,7 @@ public class EncryptionTestIT {
Set<String> keys = new HashSet<>();
for (int i = 0; i < 50; i++) {
assertTrue(keys.add(EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey())));
assertTrue(keys.add(encodeBytesToBase64String(generateKey())));
}
}
@ -241,8 +379,8 @@ public class EncryptionTestIT {
Set<String> ivs = new HashSet<>();
for (int i = 0; i < 50; i++) {
assertTrue(ivs.add(EncryptionUtils.encodeBytesToBase64String(
EncryptionUtils.randomBytes(EncryptionUtils.ivLength))));
assertTrue(ivs.add(encodeBytesToBase64String(
randomBytes(ivLength))));
}
}
@ -254,8 +392,8 @@ public class EncryptionTestIT {
Set<String> ivs = new HashSet<>();
for (int i = 0; i < 50; i++) {
assertTrue(ivs.add(EncryptionUtils.encodeBytesToBase64String(
EncryptionUtils.randomBytes(EncryptionUtils.saltLength))));
assertTrue(ivs.add(encodeBytesToBase64String(
randomBytes(saltLength))));
}
}
@ -263,13 +401,13 @@ public class EncryptionTestIT {
public void testSHA512() {
// sent to 3rd party app in cleartext
String token = "4ae5978bf5354cd284b539015d442141";
String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
String salt = encodeBytesToBase64String(randomBytes(saltLength));
// stored in database
String hashedToken = EncryptionUtils.generateSHA512(token, salt);
String hashedToken = generateSHA512(token, salt);
// check: use passed cleartext and salt to verify hashed token
assertTrue(EncryptionUtils.verifySHA512(hashedToken, token));
assertTrue(verifySHA512(hashedToken, token));
}
@ -289,9 +427,9 @@ public class EncryptionTestIT {
}
private DecryptedFolderMetadata generateFolderMetadata() throws Exception {
String metadataKey0 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String metadataKey1 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String metadataKey2 = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String metadataKey0 = encodeBytesToBase64String(generateKey());
String metadataKey1 = encodeBytesToBase64String(generateKey());
String metadataKey2 = encodeBytesToBase64String(generateKey());
HashMap<Integer, String> metadataKeys = new HashMap<>();
metadataKeys.put(0, EncryptionUtils.encryptStringAsymmetric(metadataKey0, cert));
metadataKeys.put(1, EncryptionUtils.encryptStringAsymmetric(metadataKey1, cert));
@ -345,28 +483,28 @@ public class EncryptionTestIT {
private boolean cryptFile(String fileName, String md5, byte[] key, byte[] iv, byte[] expectedAuthTag)
throws Exception {
File file = getFile(fileName);
assertEquals(md5, EncryptionUtils.getMD5Sum(file));
assertEquals(md5, getMD5Sum(file));
EncryptionUtils.EncryptedFile encryptedFile = EncryptionUtils.encryptFile(file, key, iv);
EncryptedFile encryptedFile = encryptFile(file, key, iv);
File encryptedTempFile = File.createTempFile("file", "tmp");
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
fileOutputStream.write(encryptedFile.encryptedBytes);
fileOutputStream.close();
byte[] authenticationTag = EncryptionUtils.decodeStringToBase64Bytes(encryptedFile.authenticationTag);
byte[] authenticationTag = decodeStringToBase64Bytes(encryptedFile.authenticationTag);
// verify authentication tag
assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
byte[] decryptedBytes = EncryptionUtils.decryptFile(encryptedTempFile, key, iv, authenticationTag);
byte[] decryptedBytes = decryptFile(encryptedTempFile, key, iv, authenticationTag);
File decryptedFile = File.createTempFile("file", "dec");
FileOutputStream fileOutputStream1 = new FileOutputStream(decryptedFile);
fileOutputStream1.write(decryptedBytes);
fileOutputStream1.close();
return md5.compareTo(EncryptionUtils.getMD5Sum(decryptedFile)) == 0;
return md5.compareTo(getMD5Sum(decryptedFile)) == 0;
}
private File getFile(String filename) throws IOException {

View File

@ -25,6 +25,7 @@ import android.accounts.Account;
import android.content.res.Resources;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.utils.ErrorMessageAdapter;
@ -50,7 +51,7 @@ public class ErrorMessageAdapterIT {
String errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN),
new RemoveFileOperation(PATH_TO_DELETE, false, account, false, MainApp.getAppContext()),
new RemoveFileOperation(new OCFile(PATH_TO_DELETE), false, account, false, MainApp.getAppContext()),
resources
);

View File

@ -13,6 +13,7 @@ import androidx.annotation.Nullable;
public interface CurrentAccountProvider {
/**
* Get currently active account.
* Replaced by getUser()
*
* @return Currently selected {@link Account} or first valid {@link Account} registered in OS or null, if not available at all.
*/

View File

@ -93,7 +93,7 @@ class OfflineSyncWork constructor(
val ocFolder = storageManager.getFileByPath(folderName)
Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.etag)
// check for etag change, if false, skip
val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.remotePath,
val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.encryptedFileName,
ocFolder.etagOnServer)
val result = checkEtagOperation.execute(user.toPlatformAccount(), context)
when (result.code) {

View File

@ -96,8 +96,25 @@ public class FileDataStorageManager {
this.account = account;
}
/**
* Use getFileByEncryptedRemotePath() or getFileByDecryptedRemotePath()
*/
@Deprecated
public OCFile getFileByPath(String path) {
Cursor c = getFileCursorForValue(ProviderTableMeta.FILE_PATH, path);
return getFileByEncryptedRemotePath(path);
}
public OCFile getFileByEncryptedRemotePath(String path) {
return getFileByPath(ProviderTableMeta.FILE_PATH, path);
}
public OCFile getFileByDecryptedRemotePath(String path) {
return getFileByPath(ProviderTableMeta.FILE_PATH_DECRYPTED, path);
}
private OCFile getFileByPath(String type, String path) {
Cursor c = getFileCursorForValue(type, path);
OCFile file = null;
if (c.moveToFirst()) {
file = createFileInstance(c);
@ -190,8 +207,9 @@ public class FileDataStorageManager {
cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, file.getEncryptedFileName());
cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, file.getDecryptedRemotePath());
cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, file.isEncrypted());
if (!file.isFolder()) {
cv.put(ProviderTableMeta.FILE_IS_ENCRYPTED, file.isEncrypted());
cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
}
cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
@ -215,10 +233,7 @@ public class FileDataStorageManager {
cv.put(ProviderTableMeta.FILE_RICH_WORKSPACE, file.getRichWorkspace());
boolean sameRemotePath = fileExists(file.getRemotePath());
if (sameRemotePath ||
fileExists(file.getFileId())) { // for renamed files; no more delete and create
if (sameRemotePath || fileExists(file.getFileId())) { // for renamed files; no more delete and create
if (sameRemotePath) {
OCFile oldFile = getFileByPath(file.getRemotePath());
file.setFileId(oldFile.getFileId());
@ -446,6 +461,7 @@ public class FileDataStorageManager {
cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName());
cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId());
cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath());
cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, folder.getDecryptedRemotePath());
cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties());
cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData());
@ -479,9 +495,8 @@ public class FileDataStorageManager {
cv.put(ProviderTableMeta.FILE_ENCRYPTED_NAME, file.getEncryptedFileName());
cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId());
cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
if (!file.isFolder()) {
cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
}
cv.put(ProviderTableMeta.FILE_PATH_DECRYPTED, file.getDecryptedRemotePath());
cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, account.name);
cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
@ -659,11 +674,11 @@ public class FileDataStorageManager {
if (getContentProviderClient() != null) {
try {
c = getContentProviderClient().query(
ProviderTableMeta.CONTENT_URI,
null,
ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
new String[]{account.name, file.getRemotePath() + "%"},
ProviderTableMeta.FILE_PATH + " ASC "
ProviderTableMeta.CONTENT_URI,
null,
ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
new String[]{account.name, file.getRemotePath() + "%"},
ProviderTableMeta.FILE_PATH + " ASC "
);
} catch (RemoteException e) {
Log_OC.e(TAG, e.getMessage(), e);
@ -671,11 +686,11 @@ public class FileDataStorageManager {
} else {
c = getContentResolver().query(
ProviderTableMeta.CONTENT_URI,
null,
ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
new String[]{account.name, file.getRemotePath() + "%"},
ProviderTableMeta.FILE_PATH + " ASC "
ProviderTableMeta.CONTENT_URI,
null,
ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + " LIKE ? ",
new String[]{account.name, file.getRemotePath() + "%"},
ProviderTableMeta.FILE_PATH + " ASC "
);
}
@ -692,8 +707,8 @@ public class FileDataStorageManager {
ContentValues cv = new ContentValues(); // keep construction in the loop
OCFile child = createFileInstance(c);
cv.put(
ProviderTableMeta.FILE_PATH,
targetPath + child.getRemotePath().substring(lengthOfOldPath)
ProviderTableMeta.FILE_PATH,
targetPath + child.getRemotePath().substring(lengthOfOldPath)
);
if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) {
// update link to downloaded content - but local move is not done here!
@ -961,9 +976,9 @@ public class FileDataStorageManager {
OCFile file = null;
if (c != null) {
file = new OCFile(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)));
file.setDecryptedRemotePath(getString(c, ProviderTableMeta.FILE_PATH_DECRYPTED));
file.setFileId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID)));
file.setParentId(c.getLong(c.getColumnIndex(ProviderTableMeta.FILE_PARENT)));
file.setEncryptedFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ENCRYPTED_NAME)));
file.setMimeType(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)));
file.setStoragePath(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)));
if (file.getStoragePath() == null) {
@ -995,9 +1010,9 @@ public class FileDataStorageManager {
file.setEtagInConflict(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ETAG_IN_CONFLICT)));
file.setFavorite(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_FAVORITE)) == 1);
file.setEncrypted(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_IS_ENCRYPTED)) == 1);
if (file.isEncrypted()) {
file.setFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)));
}
// if (file.isEncrypted()) {
// file.setFileName(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)));
// }
file.setMountType(WebdavEntry.MountType.values()[c.getInt(
c.getColumnIndex(ProviderTableMeta.FILE_MOUNT_TYPE))]);
file.setPreviewAvailable(c.getInt(c.getColumnIndex(ProviderTableMeta.FILE_HAS_PREVIEW)) == 1);

View File

@ -64,6 +64,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
*/
private long modificationTimestampAtLastSyncForData;
private String remotePath;
private String decryptedRemotePath;
private String localPath;
private String mimeType;
private boolean needsUpdatingWhileSaving;
@ -103,8 +104,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
* Cached after first call, until changed.
*/
private Uri exposedFileUri;
private String encryptedFileName;
/**
* Create new {@link OCFile} with given path.
@ -135,6 +134,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
modificationTimestamp = source.readLong();
modificationTimestampAtLastSyncForData = source.readLong();
remotePath = source.readString();
decryptedRemotePath = source.readString();
localPath = source.readString();
mimeType = source.readString();
needsUpdatingWhileSaving = source.readInt() == 0;
@ -152,7 +152,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
sharedWithSharee = source.readInt() == 1;
favorite = source.readInt() == 1;
encrypted = source.readInt() == 1;
encryptedFileName = source.readString();
ownerId = source.readString();
ownerDisplayName = source.readString();
mountType = (WebdavEntry.MountType) source.readSerializable();
@ -169,6 +168,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
dest.writeLong(modificationTimestamp);
dest.writeLong(modificationTimestampAtLastSyncForData);
dest.writeString(remotePath);
dest.writeString(decryptedRemotePath);
dest.writeString(localPath);
dest.writeString(mimeType);
dest.writeInt(needsUpdatingWhileSaving ? 1 : 0);
@ -186,7 +186,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
dest.writeInt(sharedWithSharee ? 1 : 0);
dest.writeInt(favorite ? 1 : 0);
dest.writeInt(encrypted ? 1 : 0);
dest.writeString(encryptedFileName);
dest.writeString(ownerId);
dest.writeString(ownerDisplayName);
dest.writeSerializable(mountType);
@ -194,34 +193,50 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
dest.writeInt(previewAvailable ? 1 : 0);
}
public String getDecryptedRemotePath() {
return remotePath;
public void setDecryptedRemotePath(String path) {
decryptedRemotePath = path;
}
/**
* Returns the remote path of the file on ownCloud
* Use decrypted remote path for every local file operation Use encrypted remote path for every dav related
* operation
*/
public String getDecryptedRemotePath() {
// Fallback
// TODO test without, on a new created folder
if (!isEncrypted() && decryptedRemotePath == null) {
decryptedRemotePath = remotePath;
}
if (isFolder()) {
if (decryptedRemotePath.endsWith(PATH_SEPARATOR)) {
return decryptedRemotePath;
} else {
return decryptedRemotePath + PATH_SEPARATOR;
}
} else {
return decryptedRemotePath;
}
}
/**
* Returns the remote path of the file on Nextcloud
* (this might be an encrypted file path, if E2E is used)
* <p>
* Use decrypted remote path for every local file operation.
* Use remote path for every dav related operation
*
* @return The remote path to the file
*/
public String getRemotePath() {
if (isEncrypted() && !isFolder()) {
String parentPath = new File(remotePath).getParent();
if (parentPath.endsWith(PATH_SEPARATOR)) {
return parentPath + getEncryptedFileName();
if (isFolder()) {
if (remotePath.endsWith(PATH_SEPARATOR)) {
return remotePath;
} else {
return parentPath + PATH_SEPARATOR + getEncryptedFileName();
return remotePath + PATH_SEPARATOR;
}
} else {
if (isFolder()) {
if (remotePath.endsWith(PATH_SEPARATOR)) {
return remotePath;
} else {
return remotePath + PATH_SEPARATOR;
}
} else {
return remotePath;
}
return remotePath;
}
}
@ -241,7 +256,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
* @return true if it is a folder
*/
public boolean isFolder() {
return MimeType.DIRECTORY.equals(mimeType);
return MimeType.DIRECTORY.equals(mimeType) || MimeType.WEBDAV_FOLDER.equals(mimeType);
}
@ -346,17 +361,40 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
* @param storage_path to set
*/
public void setStoragePath(String storage_path) {
localPath = storage_path;
if (storage_path == null) {
localPath = null;
} else {
localPath = storage_path.replaceAll("//", "/");
}
localUri = null;
exposedFileUri = null;
}
/**
* Returns the filename and "/" for the root directory
* Returns the decrypted filename and "/" for the root directory
*
* @return The name of the file
*/
public String getFileName() {
return getDecryptedFileName();
}
/**
* Returns the decrypted filename and "/" for the root directory
*
* @return The name of the file
*/
public String getDecryptedFileName() {
File f = new File(getDecryptedRemotePath());
return f.getName().length() == 0 ? ROOT_PATH : f.getName();
}
/**
* Returns the encrypted filename and "/" for the root directory
*
* @return The name of the file
*/
public String getEncryptedFileName() {
File f = new File(remotePath);
return f.getName().length() == 0 ? ROOT_PATH : f.getName();
}
@ -386,6 +424,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
private void resetData() {
fileId = -1;
remotePath = null;
decryptedRemotePath = null;
parentId = 0;
localPath = null;
mimeType = null;
@ -408,7 +447,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
sharedWithSharee = false;
favorite = false;
encrypted = false;
encryptedFileName = null;
mountType = WebdavEntry.MountType.INTERNAL;
richWorkspace = "";
}
@ -464,8 +502,16 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
public String toString() {
String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, " +
"parentId=%s, etag=%s, favourite=%s]";
return String.format(asString, fileId, getFileName(), mimeType, isDown(), localPath, remotePath, parentId,
etag, favorite);
return String.format(asString,
fileId,
getFileName(),
mimeType,
isDown(),
localPath,
remotePath,
parentId,
etag,
favorite);
}
public void setEtag(String etag) {
@ -652,10 +698,6 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
return this.richWorkspace;
}
public String getEncryptedFileName() {
return this.encryptedFileName;
}
public void setFileId(long fileId) {
this.fileId = fileId;
}
@ -767,8 +809,4 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
public void setRichWorkspace(String richWorkspace) {
this.richWorkspace = richWorkspace;
}
public void setEncryptedFileName(String encryptedFileName) {
this.encryptedFileName = encryptedFileName;
}
}

View File

@ -332,7 +332,7 @@ public final class ThumbnailsCacheManager {
GetMethod getMethod = null;
try {
String uri = mClient.getBaseUri() + "/index.php/core/preview.png?file="
+ URLEncoder.encode(file.getRemotePath())
+ URLEncoder.encode(file.getRemotePath())
+ "&x=" + pxW + "&y=" + pxH + "&a=1&mode=cover&forceIcon=0";
getMethod = new GetMethod(uri);
@ -619,7 +619,7 @@ public final class ThumbnailsCacheManager {
String uri;
if (file instanceof OCFile) {
uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
pxW + "/" + pxH + Uri.encode(file.getRemotePath(), "/");
pxW + "/" + pxH + Uri.encode(file.getRemotePath(), "/");
} else {
uri = mClient.getBaseUri() + "/index.php/apps/files_trashbin/preview?fileId=" +
file.getLocalId() + "&x=" + pxW + "&y=" + pxH;

View File

@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
public static final int DB_VERSION = 55;
public static final int DB_VERSION = 56;
private ProviderMeta() {
// No instance
@ -89,6 +89,7 @@ public class ProviderMeta {
public static final String FILE_CONTENT_TYPE = "content_type";
public static final String FILE_STORAGE_PATH = "media_path";
public static final String FILE_PATH = "path";
public static final String FILE_PATH_DECRYPTED = "path_decrypted";
public static final String FILE_ACCOUNT_OWNER = "file_owner";
public static final String FILE_LAST_SYNC_DATE = "last_sync_date";// _for_properties, but let's keep it as it is
public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data";

View File

@ -280,7 +280,7 @@ public class FileMenuFilter {
List<Integer> toHide,
OCCapability capability
) {
if (deviceInfo.editorSupported()) {
if (deviceInfo.editorSupported() || files.iterator().next().isEncrypted()) {
toHide.add(R.id.action_edit);
return;
}

View File

@ -292,13 +292,13 @@ public class FileDownloader extends Service
*/
public void cancel(Account account, OCFile file) {
Pair<DownloadFileOperation, String> removeResult =
mPendingDownloads.remove(account.name, file.getRemotePath());
mPendingDownloads.remove(account.name, file.getRemotePath());
DownloadFileOperation download = removeResult.first;
if (download != null) {
download.cancel();
} else {
if (mCurrentDownload != null && mCurrentAccount != null &&
mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
account.name.equals(mCurrentAccount.name)) {
mCurrentDownload.cancel();
}

View File

@ -21,59 +21,246 @@
package com.owncloud.android.operations;
import android.accounts.Account;
import android.content.Context;
import android.os.Build;
import android.util.Pair;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeType;
import java.io.File;
import java.util.UUID;
import androidx.annotation.RequiresApi;
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
/**
* Access to remote operation performing the creation of a new folder in the ownCloud server.
* Save the new folder in Database
* Access to remote operation performing the creation of a new folder in the ownCloud server. Save the new folder in
* Database
*/
public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener{
public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
private static final String TAG = CreateFolderOperation.class.getSimpleName();
protected String mRemotePath;
private boolean mCreateFullPath;
protected String remotePath;
private RemoteFile createdRemoteFolder;
private Account account;
private Context context;
/**
* Constructor
*
* @param createFullPath 'True' means that all the ancestor folders should be created
* if don't exist yet.
*/
public CreateFolderOperation(String remotePath, boolean createFullPath) {
mRemotePath = remotePath;
mCreateFullPath = createFullPath;
public CreateFolderOperation(String remotePath, Account account, Context context) {
this.remotePath = remotePath;
this.account = account;
this.context = context;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = new CreateFolderRemoteOperation(mRemotePath, mCreateFullPath).execute(client);
String remoteParentPath = new File(getRemotePath()).getParent();
remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
OCFile parent = getStorageManager().getFileByDecryptedRemotePath(remoteParentPath);
// check if any parent is encrypted
boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
if (encryptedAncestor) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return encryptedCreate(parent, remoteParentPath, client);
} else {
Log_OC.e(TAG, "Encrypted upload on old Android API");
return new RemoteOperationResult(RemoteOperationResult.ResultCode.OLD_ANDROID_API);
}
} else {
return normalCreate(client);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private RemoteOperationResult encryptedCreate(OCFile parent, String remoteParentPath, OwnCloudClient client) {
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
String publicKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PUBLIC_KEY);
String token = null;
Boolean metadataExists;
DecryptedFolderMetadata metadata;
String encryptedRemotePath = null;
String filename = new File(remotePath).getName();
try {
// lock folder
token = EncryptionUtils.lockFolder(parent, client);
// get metadata
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
client,
privateKey,
publicKey);
metadataExists = metadataPair.first;
metadata = metadataPair.second;
// check if filename already exists
for (String key : metadata.getFiles().keySet()) {
DecryptedFolderMetadata.DecryptedFile file = metadata.getFiles().get(key);
if (file != null && filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
}
}
// generate new random file name, check if it exists in metadata
String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
while (metadata.getFiles().get(encryptedFileName) != null) {
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
}
encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath,
true,
token)
.execute(client);
if (result.isSuccess()) {
RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(encryptedRemotePath)
.execute(client);
createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
OCFile newDir = new OCFile(createdRemoteFolder.getRemotePath());
newDir.setMimeType(MimeType.DIRECTORY);
newDir.setParentId(parent.getFileId());
newDir.setRemoteId(createdRemoteFolder.getRemoteId());
newDir.setModificationTimestamp(System.currentTimeMillis());
newDir.setEncrypted(true);
newDir.setPermissions(createdRemoteFolder.getPermissions());
newDir.setDecryptedRemotePath(parent.getDecryptedRemotePath() + filename + "/");
getStorageManager().saveFile(newDir);
RemoteOperationResult encryptionOperationResult = new ToggleEncryptionRemoteOperation(
newDir.getLocalId(),
newDir.getRemotePath(),
true)
.execute(client);
if (!encryptionOperationResult.isSuccess()) {
throw new RuntimeException("Error creating encrypted subfolder!");
}
// Key, always generate new one
byte[] key = EncryptionUtils.generateKey();
// IV, always generate new one
byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
// update metadata
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
data.setFilename(filename);
data.setMimetype(MimeType.WEBDAV_FOLDER);
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
decryptedFile.setEncrypted(data);
decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
metadata.getFiles().put(encryptedFileName, decryptedFile);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
privateKey);
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata
EncryptionUtils.uploadMetadata(parent,
serializedFolderMetadata,
token,
client,
metadataExists);
} else {
// revert to sane state in case of any error
Log_OC.e(TAG, remotePath + " hasn't been created");
}
return result;
} catch (Exception e) {
// // revert to latest metadata
// try {
// Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
// client,
// privateKey,
// publicKey);
// } catch (Exception metadataException) {
// throw new RuntimeException(metadataException);
// }
if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) {
throw new RuntimeException("Could not clean up after failing folder creation!");
}
// remove folder
if (encryptedRemotePath != null) {
RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
parent.getLocalId(),
account,
context,
filename).execute(client);
if (!removeResult.isSuccess()) {
throw new RuntimeException("Could not clean up after failing folder creation!");
}
}
// TODO do better
return new RemoteOperationResult(e);
} finally {
// unlock folder
if (token != null) {
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
if (!unlockFolderResult.isSuccess()) {
// TODO do better
throw new RuntimeException("Could not unlock folder!");
}
}
}
}
private RemoteOperationResult normalCreate(OwnCloudClient client) {
RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
if (result.isSuccess()) {
RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(mRemotePath)
RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(remotePath)
.execute(client);
createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
saveFolderInDB();
} else {
Log_OC.e(TAG, mRemotePath + " hasn't been created");
Log_OC.e(TAG, remotePath + " hasn't been created");
}
return result;
@ -87,36 +274,34 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
}
private void onCreateRemoteFolderOperationFinish(RemoteOperationResult result) {
if (result.isSuccess()) {
saveFolderInDB();
} else {
Log_OC.e(TAG, mRemotePath + " hasn't been created");
}
if (result.isSuccess()) {
saveFolderInDB();
} else {
Log_OC.e(TAG, remotePath + " hasn't been created");
}
}
/**
* Save new directory in local database.
*/
private void saveFolderInDB() {
if (mCreateFullPath && getStorageManager().
getFileByPath(FileStorageUtils.getParentPath(mRemotePath)) == null){// When parent
// of remote path
// is not created
String[] subFolders = mRemotePath.split(PATH_SEPARATOR);
if (getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) {
// When parent of remote path is not created
String[] subFolders = remotePath.split(PATH_SEPARATOR);
String composedRemotePath = ROOT_PATH;
// For each ancestor folders create them recursively
for (String subFolder : subFolders) {
if (!subFolder.isEmpty()) {
composedRemotePath = composedRemotePath + subFolder + PATH_SEPARATOR;
mRemotePath = composedRemotePath;
remotePath = composedRemotePath;
saveFolderInDB();
}
}
} else { // Create directory on DB
OCFile newDir = new OCFile(mRemotePath);
OCFile newDir = new OCFile(remotePath);
newDir.setMimeType(MimeType.DIRECTORY);
long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(mRemotePath)).getFileId();
long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId();
newDir.setParentId(parentId);
newDir.setRemoteId(createdRemoteFolder.getRemoteId());
newDir.setModificationTimestamp(System.currentTimeMillis());
@ -124,11 +309,11 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
newDir.setPermissions(createdRemoteFolder.getPermissions());
getStorageManager().saveFile(newDir);
Log_OC.d(TAG, "Create directory " + mRemotePath + " in Database");
Log_OC.d(TAG, "Create directory " + remotePath + " in Database");
}
}
public String getRemotePath() {
return mRemotePath;
return remotePath;
}
}

View File

@ -87,8 +87,12 @@ public class DownloadFileOperation extends RemoteOperation {
public String getSavePath() {
if (file.getStoragePath() != null) {
File parentFile = new File(file.getStoragePath()).getParentFile();
if (parentFile != null && !parentFile.exists()) {
parentFile.mkdirs();
}
File path = new File(file.getStoragePath()); // re-downloads should be done over the original file
if (path.canWrite()) {
if (path.canWrite() || parentFile != null && parentFile.canWrite()) {
return path.getAbsolutePath();
}
}
@ -117,7 +121,7 @@ public class DownloadFileOperation extends RemoteOperation {
file.getRemotePath().lastIndexOf('.') + 1));
} catch (IndexOutOfBoundsException e) {
Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " +
file.getRemotePath());
file.getRemotePath());
}
}
if (mimeType == null) {

View File

@ -46,6 +46,7 @@ import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.utils.DataHolderUtil;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeType;
import com.owncloud.android.utils.MimeTypeUtil;
import java.util.ArrayList;
@ -57,6 +58,8 @@ import java.util.Vector;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
/**
* Remote operation performing the synchronization of the list of files contained
@ -241,7 +244,7 @@ public class RefreshFolderOperation extends RemoteOperation {
if (!mSyncFullAccount) {
sendLocalBroadcast(
EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
);
}
@ -251,7 +254,7 @@ public class RefreshFolderOperation extends RemoteOperation {
if (!mSyncFullAccount) {
sendLocalBroadcast(
EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
);
}
@ -422,7 +425,7 @@ public class RefreshFolderOperation extends RemoteOperation {
// update permission
mLocalFolder.setPermissions(remoteFolder.getPermissions());
// update richWorkpace
// update richWorkspace
mLocalFolder.setRichWorkspace(remoteFolder.getRichWorkspace());
DecryptedFolderMetadata metadata = getDecryptedFolderMetadata(encryptedAncestor);
@ -475,6 +478,10 @@ public class RefreshFolderOperation extends RemoteOperation {
}
// save updated contents in local database
// update file name for encrypted files
if (metadata != null) {
updateFileNameForEncryptedFile(metadata, mLocalFolder);
}
mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
mChildren = updatedFiles;
@ -492,15 +499,25 @@ public class RefreshFolderOperation extends RemoteOperation {
}
private void updateFileNameForEncryptedFile(@NonNull DecryptedFolderMetadata metadata, OCFile updatedFile) {
updatedFile.setEncryptedFileName(updatedFile.getFileName());
try {
String decryptedFileName = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted()
.getFilename();
String mimetype = metadata.getFiles().get(updatedFile.getFileName()).getEncrypted().getMimetype();
updatedFile.setFileName(decryptedFileName);
OCFile parentFile = mStorageManager.getFileById(updatedFile.getParentId());
String decryptedRemotePath = parentFile.getDecryptedRemotePath() + decryptedFileName;
if (updatedFile.isFolder()) {
decryptedRemotePath += "/";
}
updatedFile.setDecryptedRemotePath(decryptedRemotePath);
if (mimetype == null || mimetype.isEmpty()) {
updatedFile.setMimeType("application/octet-stream");
if (updatedFile.isFolder()) {
updatedFile.setMimeType(MimeType.DIRECTORY);
} else {
updatedFile.setMimeType("application/octet-stream");
}
} else {
updatedFile.setMimeType(mimetype);
}
@ -516,7 +533,11 @@ public class RefreshFolderOperation extends RemoteOperation {
updatedFile.setModificationTimestampAtLastSyncForData(
localFile.getModificationTimestampAtLastSyncForData()
);
updatedFile.setStoragePath(localFile.getStoragePath());
if (localFile.isEncrypted()) {
updatedFile.setStoragePath(mLocalFolder.getRemotePath() + PATH_SEPARATOR + localFile.getFileName());
} else {
updatedFile.setStoragePath(localFile.getStoragePath());
}
// eTag will not be updated unless file CONTENTS are synchronized
if (!updatedFile.isFolder() && localFile.isDown() &&
@ -555,8 +576,11 @@ public class RefreshFolderOperation extends RemoteOperation {
for (OCFile file : localFiles) {
String remotePath = file.getRemotePath();
if (metadata != null && !file.isFolder()) {
if (metadata != null) {
remotePath = file.getParentRemotePath() + file.getEncryptedFileName();
if (file.isFolder() && !remotePath.endsWith(PATH_SEPARATOR)) {
remotePath = remotePath + PATH_SEPARATOR;
}
}
localFilesMap.put(remotePath, file);
}

View File

@ -42,7 +42,6 @@ import com.owncloud.android.utils.MimeTypeUtil;
public class RemoveFileOperation extends SyncOperation {
private OCFile fileToRemove;
private String remotePath;
private boolean onlyLocalCopy;
private Account account;
private boolean inBackground;
@ -52,14 +51,15 @@ public class RemoveFileOperation extends SyncOperation {
/**
* Constructor
*
* @param remotePath RemotePath of the OCFile instance describing the remote file or
* folder to remove from the server
* @param onlyLocalCopy When 'true', and a local copy of the file exists, only this is
* removed.
* @param fileToRemove OCFile instance describing the remote file or folder to remove from the server
* @param onlyLocalCopy When 'true', and a local copy of the file exists, only this is removed.
*/
public RemoveFileOperation(String remotePath, boolean onlyLocalCopy, Account account, boolean inBackground,
public RemoveFileOperation(OCFile fileToRemove,
boolean onlyLocalCopy,
Account account,
boolean inBackground,
Context context) {
this.remotePath = remotePath;
this.fileToRemove = fileToRemove;
this.onlyLocalCopy = onlyLocalCopy;
this.account = account;
this.inBackground = inBackground;
@ -90,8 +90,6 @@ public class RemoveFileOperation extends SyncOperation {
RemoteOperationResult result = null;
RemoteOperation operation;
fileToRemove = getStorageManager().getFileByPath(remotePath);
if (MimeTypeUtil.isImage(fileToRemove.getMimeType())) {
// store resized image
ThumbnailsCacheManager.generateResizedImage(fileToRemove);
@ -99,20 +97,21 @@ public class RemoveFileOperation extends SyncOperation {
boolean localRemovalFailed = false;
if (!onlyLocalCopy) {
if (fileToRemove.isEncrypted() &&
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
OCFile parent = getStorageManager().getFileByPath(fileToRemove.getParentRemotePath());
operation = new RemoveRemoteEncryptedFileOperation(remotePath, parent.getLocalId(), account, context,
fileToRemove.getEncryptedFileName());
operation = new RemoveRemoteEncryptedFileOperation(fileToRemove.getRemotePath(),
parent.getLocalId(),
account,
context,
fileToRemove.getEncryptedFileName());
} else {
operation = new RemoveFileRemoteOperation(remotePath);
operation = new RemoveFileRemoteOperation(fileToRemove.getDecryptedRemotePath());
}
result = operation.execute(client);
if (result.isSuccess() || result.getCode() == ResultCode.FILE_NOT_FOUND) {
localRemovalFailed = !(getStorageManager().removeFile(fileToRemove, true, true));
}
} else {
localRemovalFailed = !(getStorageManager().removeFile(fileToRemove, false, true));
if (!localRemovalFailed) {
@ -126,5 +125,4 @@ public class RemoveFileOperation extends SyncOperation {
return result;
}
}

View File

@ -41,8 +41,19 @@ import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
import com.owncloud.android.utils.EncryptionUtils;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import androidx.annotation.RequiresApi;
/**
@ -68,7 +79,10 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
* @param remotePath RemotePath of the remote file or folder to remove from the server
* @param parentId local id of parent folder
*/
RemoveRemoteEncryptedFileOperation(String remotePath, String parentId, Account account, Context context,
RemoveRemoteEncryptedFileOperation(String remotePath,
String parentId,
Account account,
Context context,
String fileName) {
this.remotePath = remotePath;
this.parentId = parentId;
@ -90,8 +104,6 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
String privateKey = arbitraryDataProvider.getValue(account.name, EncryptionUtils.PRIVATE_KEY);
// unlock
try {
// Lock folder
RemoteOperationResult lockFileOperationResult = new LockFileRemoteOperation(parentId).execute(client);
@ -122,6 +134,7 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
// delete file remote
delete = new DeleteMethod(client.getWebdavUri() + WebdavUtils.encodePath(remotePath));
delete.setQueryString(new NameValuePair[]{new NameValuePair(E2E_TOKEN, token)});
int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
delete.getResponseBodyAsString(); // exhaust the response, although not interesting
@ -136,8 +149,9 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata
RemoteOperationResult uploadMetadataOperationResult = new UpdateMetadataRemoteOperation(parentId,
serializedFolderMetadata, token).execute(client);
RemoteOperationResult uploadMetadataOperationResult =
new UpdateMetadataRemoteOperation(parentId,
serializedFolderMetadata, token).execute(client);
if (!uploadMetadataOperationResult.isSuccess()) {
throw new RemoteOperationFailedException("Metadata not uploaded!");
@ -145,7 +159,14 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
// return success
return result;
} catch (Exception e) {
} catch (NoSuchAlgorithmException |
IOException |
InvalidKeyException |
InvalidAlgorithmParameterException |
NoSuchPaddingException |
BadPaddingException |
IllegalBlockSizeException |
InvalidKeySpecException e) {
result = new RemoteOperationResult(e);
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
@ -167,5 +188,4 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
return result;
}
}

View File

@ -90,8 +90,11 @@ public class RenameFileOperation extends SyncOperation {
return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE);
}
result = new RenameFileRemoteOperation(file.getFileName(), file.getRemotePath(), newName,
file.isFolder()).execute(client);
result = new RenameFileRemoteOperation(file.getFileName(),
file.getRemotePath(),
newName,
file.isFolder())
.execute(client);
if (result.isSuccess()) {
if (file.isFolder()) {
@ -104,7 +107,7 @@ public class RenameFileOperation extends SyncOperation {
}
} catch (IOException e) {
Log_OC.e(TAG, "Rename " + file.getRemotePath() + " to " + ((newRemotePath ==null) ?
Log_OC.e(TAG, "Rename " + file.getRemotePath() + " to " + ((newRemotePath == null) ?
newName : newRemotePath) + ": " +
(result!= null ? result.getLogMessage() : ""), e);
}

View File

@ -21,7 +21,6 @@
package com.owncloud.android.operations;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;

View File

@ -20,14 +20,14 @@
package com.owncloud.android.operations;
class UploadException extends Exception {
public class UploadException extends Exception {
private static final long serialVersionUID = 5931153844211429915L;
UploadException() {
public UploadException() {
super();
}
UploadException(String message) {
public UploadException(String message) {
super(message);
}
}

View File

@ -28,8 +28,8 @@ import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.google.gson.reflect.TypeToken;
import com.nextcloud.client.device.BatteryStatus;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.network.Connectivity;
@ -51,11 +51,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation;
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
@ -84,7 +80,6 @@ import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@ -459,52 +454,19 @@ public class UploadFileOperation extends SyncOperation {
}
/***** E2E *****/
// Lock folder
LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
if (lockFileOperationResult.isSuccess()) {
token = (String) lockFileOperationResult.getData().get(0);
// immediately store it
mUpload.setFolderUnlockToken(token);
uploadsStorageManager.updateUpload(mUpload);
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
throw new UploadException("Forbidden! Please try again later.)");
} else {
throw new UploadException("Unknown error!");
}
token = EncryptionUtils.lockFolder(parentFile, client);
// immediately store it
mUpload.setFolderUnlockToken(token);
uploadsStorageManager.updateUpload(mUpload);
// Update metadata
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
client,
privateKey,
publicKey);
DecryptedFolderMetadata metadata;
if (getMetadataOperationResult.isSuccess()) {
metadataExists = true;
// decrypt metadata
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
});
metadata = EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
// new metadata
metadata = new DecryptedFolderMetadata();
metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
metadata.getMetadata().setMetadataKeys(new HashMap<>());
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
} else {
// TODO error
throw new UploadException("something wrong");
}
metadataExists = metadataPair.first;
DecryptedFolderMetadata metadata = metadataPair.second;
/**** E2E *****/
@ -544,8 +506,6 @@ public class UploadFileOperation extends SyncOperation {
encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
}
mFile.setEncryptedFileName(encryptedFileName);
File encryptedTempFile = File.createTempFile("encFile", encryptedFileName);
FileOutputStream fileOutputStream = new FileOutputStream(encryptedTempFile);
fileOutputStream.write(encryptedFile.encryptedBytes);
@ -561,7 +521,7 @@ public class UploadFileOperation extends SyncOperation {
// this basically means that the file is on SD card
// try to copy file to temporary dir if it doesn't exist
String temporalPath = FileStorageUtils.getInternalTemporalPath(mAccount.name, mContext) +
mFile.getRemotePath();
mFile.getRemotePath();
mFile.setStoragePath(temporalPath);
temporalFile = new File(temporalPath);
@ -598,12 +558,18 @@ public class UploadFileOperation extends SyncOperation {
mUploadOperation = new ChunkedFileUploadRemoteOperation(encryptedTempFile.getAbsolutePath(),
mFile.getParentRemotePath() + encryptedFileName,
mFile.getMimeType(), mFile.getEtagInConflict(),
timeStamp, onWifiConnection);
mFile.getMimeType(),
mFile.getEtagInConflict(),
timeStamp,
onWifiConnection,
token);
} else {
mUploadOperation = new UploadFileRemoteOperation(encryptedTempFile.getAbsolutePath(),
mFile.getParentRemotePath() + encryptedFileName, mFile.getMimeType(),
mFile.getEtagInConflict(), timeStamp);
mFile.getParentRemotePath() + encryptedFileName,
mFile.getMimeType(),
mFile.getEtagInConflict(),
timeStamp,
token);
}
for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
@ -623,10 +589,13 @@ public class UploadFileOperation extends SyncOperation {
}
if (result.isSuccess()) {
// upload metadata
mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName());
mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName);
// update metadata
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
data.setFilename(mFile.getFileName());
data.setFilename(mFile.getDecryptedFileName());
data.setMimetype(mFile.getMimeType());
data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
@ -641,22 +610,11 @@ public class UploadFileOperation extends SyncOperation {
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
// upload metadata
RemoteOperationResult uploadMetadataOperationResult;
if (metadataExists) {
// update metadata
UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata, token);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
} else {
// store metadata
StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
}
if (!uploadMetadataOperationResult.isSuccess()) {
throw new UploadException();
}
EncryptionUtils.uploadMetadata(parentFile,
serializedFolderMetadata,
token,
client,
metadataExists);
}
} catch (FileNotFoundException e) {
Log_OC.d(TAG, mFile.getStoragePath() + " not exists anymore");
@ -689,15 +647,20 @@ public class UploadFileOperation extends SyncOperation {
if (result.isSuccess()) {
handleSuccessfulUpload(temporalFile, expectedFile, originalFile, client);
RemoteOperationResult unlockFolderResult = unlockFolder(parentFile, client, token);
if (!unlockFolderResult.isSuccess()) {
return unlockFolderResult;
}
} else if (result.getCode() == ResultCode.SYNC_CONFLICT) {
getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
}
// unlock must be done always
// TODO check if in good state
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parentFile,
client,
token);
if (!unlockFolderResult.isSuccess()) {
return unlockFolderResult;
}
// delete temporal file
if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) {
Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath());
@ -706,14 +669,6 @@ public class UploadFileOperation extends SyncOperation {
return result;
}
private RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
if (token != null) {
return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
} else {
return new RemoteOperationResult(new Exception("No token available"));
}
}
private RemoteOperationResult checkConditions(File originalFile) {
RemoteOperationResult remoteOperationResult = null;
@ -794,7 +749,7 @@ public class UploadFileOperation extends SyncOperation {
// this basically means that the file is on SD card
// try to copy file to temporary dir if it doesn't exist
String temporalPath = FileStorageUtils.getInternalTemporalPath(mAccount.name, mContext) +
mFile.getRemotePath();
mFile.getRemotePath();
mFile.setStoragePath(temporalPath);
temporalFile = new File(temporalPath);
@ -830,12 +785,13 @@ public class UploadFileOperation extends SyncOperation {
boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
mFile.getRemotePath(), mFile.getMimeType(),
mFile.getRemotePath(),
mFile.getMimeType(),
mFile.getEtagInConflict(),
timeStamp, onWifiConnection);
} else {
mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
mFile.getRemotePath(), mFile.getMimeType(), mFile.getEtagInConflict(), timeStamp);
}
for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
@ -1035,7 +991,7 @@ public class UploadFileOperation extends SyncOperation {
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
RemoteOperationResult result = operation.execute(client);
if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true);
SyncOperation syncOp = new CreateFolderOperation(pathToGrant, getAccount(), getContext());
result = syncOp.execute(client, getStorageManager());
}
if (result.isSuccess()) {

View File

@ -472,7 +472,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
String newDirPath = targetFolder.getRemotePath() + displayName + PATH_SEPARATOR;
FileDataStorageManager storageManager = targetFolder.getStorageManager();
RemoteOperationResult result = new CreateFolderOperation(newDirPath, true)
RemoteOperationResult result = new CreateFolderOperation(newDirPath,
accountManager.getCurrentAccount(),
getContext())
.execute(targetFolder.getClient(), storageManager);
if (!result.isSuccess()) {
@ -581,8 +583,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
recursiveRevokePermission(document);
RemoteOperationResult result = new RemoveFileOperation(document.getRemotePath(), false,
document.getAccount(), true, context)
OCFile file = document.getStorageManager().getFileByPath(document.getRemotePath());
RemoteOperationResult result = new RemoveFileOperation(file,
false,
document.getAccount(),
true,
context)
.execute(document.getClient(), document.getStorageManager());
if (!result.isSuccess()) {

View File

@ -692,6 +692,7 @@ public class FileContentProvider extends ContentProvider {
+ ProviderTableMeta.FILE_NAME + TEXT
+ ProviderTableMeta.FILE_ENCRYPTED_NAME + TEXT
+ ProviderTableMeta.FILE_PATH + TEXT
+ ProviderTableMeta.FILE_PATH_DECRYPTED + TEXT
+ ProviderTableMeta.FILE_PARENT + INTEGER
+ ProviderTableMeta.FILE_CREATION + INTEGER
+ ProviderTableMeta.FILE_MODIFIED + INTEGER
@ -2193,6 +2194,25 @@ public class FileContentProvider extends ContentProvider {
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
if (oldVersion < 56 && newVersion >= 56) {
Log_OC.i(SQL, "Entering in the #56 add decrypted remote path");
db.beginTransaction();
try {
// Add synced.name_collision_policy
db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
ADD_COLUMN + ProviderTableMeta.FILE_PATH_DECRYPTED + " TEXT "); // strin
upgraded = true;
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (!upgraded) {
Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
}
}
}
}

View File

@ -92,7 +92,6 @@ public class OperationsService extends Service {
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_NEWNAME = "NEWNAME";
public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY";
public static final String EXTRA_CREATE_FULL_PATH = "CREATE_FULL_PATH";
public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
public static final String EXTRA_RESULT = "RESULT";
public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
@ -636,17 +635,19 @@ public class OperationsService extends Service {
case ACTION_REMOVE:
// Remove file or folder
remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
OCFile file = operationIntent.getParcelableExtra(EXTRA_FILE);
boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
boolean inBackground = operationIntent.getBooleanExtra(EXTRA_IN_BACKGROUND, false);
operation = new RemoveFileOperation(remotePath, onlyLocalCopy, account, inBackground,
getApplicationContext());
operation = new RemoveFileOperation(file,
onlyLocalCopy,
account,
inBackground,
getApplicationContext());
break;
case ACTION_CREATE_FOLDER:
remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
operation = new CreateFolderOperation(remotePath, createFullPath);
operation = new CreateFolderOperation(remotePath, account, getApplicationContext());
break;
case ACTION_SYNC_FILE:

View File

@ -151,14 +151,14 @@ class SyncFolderHandler extends Handler {
return;
}
Pair<SynchronizeFolderOperation, String> removeResult = mPendingOperations.remove(account.name,
file.getRemotePath());
file.getRemotePath());
SynchronizeFolderOperation synchronization = removeResult.first;
if (synchronization != null) {
synchronization.cancel();
} else {
// TODO synchronize?
if (mCurrentSyncOperation != null && mCurrentAccount != null &&
mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
account.name.equals(mCurrentAccount.name)) {
mCurrentSyncOperation.cancel();
}

View File

@ -347,7 +347,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
if (mCancellation && i <files.size()) {
Log_OC.d(TAG,
"Leaving synchronization before synchronizing " + files.get(i).getRemotePath() +
"Leaving synchronization before synchronizing " + files.get(i).getRemotePath() +
" due to cancellation request");
}
}

View File

@ -710,12 +710,12 @@ public class FileDisplayActivity extends FileActivity
boolean success) {
FileFragment secondFragment = getSecondFragment();
boolean waitedPreview = mWaitingToPreview != null
&& mWaitingToPreview.getRemotePath().equals(downloadedRemotePath);
&& mWaitingToPreview.getRemotePath().equals(downloadedRemotePath);
if (secondFragment instanceof FileDetailFragment) {
FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment;
OCFile fileInFragment = detailsFragment.getFile();
if (fileInFragment != null &&
!downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
!downloadedRemotePath.equals(fileInFragment.getRemotePath())) {
// the user browsed to other file ; forget the automatic preview
mWaitingToPreview = null;
@ -1326,9 +1326,9 @@ public class FileDisplayActivity extends FileActivity
} else {
OCFile currentFile = (getFile() == null) ? null :
getStorageManager().getFileByPath(getFile().getRemotePath());
getStorageManager().getFileByPath(getFile().getRemotePath());
OCFile currentDir = (getCurrentDir() == null) ? null :
getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
if (currentDir == null) {
// current folder was removed from the server
@ -1463,7 +1463,7 @@ public class FileDisplayActivity extends FileActivity
boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name);
OCFile currentDir = getCurrentDir();
boolean isDescendant = currentDir != null && uploadedRemotePath != null &&
uploadedRemotePath.startsWith(currentDir.getRemotePath());
uploadedRemotePath.startsWith(currentDir.getRemotePath());
if (sameAccount && isDescendant) {
String linkedToRemotePath =
@ -1588,13 +1588,13 @@ public class FileDisplayActivity extends FileActivity
OCFile currentDir = getCurrentDir();
return currentDir != null &&
downloadedRemotePath != null &&
downloadedRemotePath.startsWith(currentDir.getRemotePath());
downloadedRemotePath.startsWith(currentDir.getRemotePath());
}
private boolean isAscendant(String linkedToRemotePath) {
OCFile currentDir = getCurrentDir();
return currentDir != null &&
currentDir.getRemotePath().startsWith(linkedToRemotePath);
currentDir.getRemotePath().startsWith(linkedToRemotePath);
}
private boolean isSameAccount(Intent intent) {
@ -2439,11 +2439,11 @@ public class FileDisplayActivity extends FileActivity
public void cancelTransference(OCFile file) {
getFileOperationsHelper().cancelTransference(file);
if (mWaitingToPreview != null &&
mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) {
mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) {
mWaitingToPreview = null;
}
if (mWaitingToSend != null &&
mWaitingToSend.getRemotePath().equals(file.getRemotePath())) {
mWaitingToSend.getRemotePath().equals(file.getRemotePath())) {
mWaitingToSend = null;
}
onTransferStateChanged(file, false, false);

View File

@ -38,7 +38,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources.NotFoundException;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -1106,9 +1105,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
} else {
OCFile currentFile = (mFile == null) ? null :
getStorageManager().getFileByPath(mFile.getRemotePath());
getStorageManager().getFileByPath(mFile.getRemotePath());
OCFile currentDir = (getCurrentFolder() == null) ? null :
getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
if (currentDir == null) {
// current folder was removed from the server

View File

@ -258,7 +258,10 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
int filesSize = mFiles.size();
for (int i = 0; i < filesSize; i++) {
if (mFiles.get(i).getRemoteId().equals(fileId)) {
mFiles.get(i).setEncrypted(encrypted);
OCFile file = mFiles.get(i);
file.setEncrypted(encrypted);
mStorageManager.saveFile(file);
break;
}
}
@ -555,7 +558,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
if (holder instanceof OCFileListGridItemViewHolder) {
OCFileListGridItemViewHolder gridItemViewHolder = (OCFileListGridItemViewHolder) holder;
gridItemViewHolder.fileName.setText(file.getFileName());
gridItemViewHolder.fileName.setText(file.getDecryptedFileName());
if (gridView && gridImage) {
gridItemViewHolder.fileName.setVisibility(View.GONE);

View File

@ -22,7 +22,6 @@
package com.owncloud.android.ui.dialog;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
@ -48,9 +47,7 @@ import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.Template;
import com.owncloud.android.files.CreateFileFromTemplateOperation;
import com.owncloud.android.files.FetchTemplateOperation;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;

View File

@ -137,8 +137,8 @@ public class CreateFolderDialogFragment
return;
}
String path = mParentFolder.getRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
((ComponentsGetter) getActivity()).getFileOperationsHelper().createFolder(path, false);
String path = mParentFolder.getDecryptedRemotePath() + newFolderName + OCFile.PATH_SEPARATOR;
((ComponentsGetter) getActivity()).getFileOperationsHelper().createFolder(path);
}
}
}

View File

@ -236,7 +236,7 @@ public class SetupEncryptionDialogFragment extends DialogFragment {
return dialog;
}
private class DownloadKeysAsyncTask extends AsyncTask<Void, Void, String> {
public class DownloadKeysAsyncTask extends AsyncTask<Void, Void, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
@ -316,7 +316,7 @@ public class SetupEncryptionDialogFragment extends DialogFragment {
}
}
private class GenerateNewKeysAsyncTask extends AsyncTask<Void, Void, String> {
public class GenerateNewKeysAsyncTask extends AsyncTask<Void, Void, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();

View File

@ -607,7 +607,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
} else {
// Get public share
publicShare = fileDataStorageManager.getFirstShareByPathAndType(file.getRemotePath(),
ShareType.PUBLIC_LINK, "");
ShareType.PUBLIC_LINK, "");
// Update public share section
updatePublicShareSection();

View File

@ -125,16 +125,20 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
ThemeUtils.getDefaultDisplayNameForRootFolder(getContext())));
OCCapability capability = fileActivity.getCapabilities();
if (capability.getRichDocuments().isTrue() && capability.getRichDocumentsDirectEditing().isTrue() &&
if (capability.getRichDocuments().isTrue() &&
capability.getRichDocumentsDirectEditing().isTrue() &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
capability.getRichDocumentsTemplatesAvailable().isTrue()) {
capability.getRichDocumentsTemplatesAvailable().isTrue() &&
!file.isEncrypted()) {
templates.setVisibility(View.VISIBLE);
}
String json = new ArbitraryDataProvider(getContext().getContentResolver())
.getValue(user.toPlatformAccount(), ArbitraryDataProvider.DIRECT_EDITING);
if (!json.isEmpty() && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (!json.isEmpty() &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
!file.isEncrypted()) {
DirectEditing directEditing = new Gson().fromJson(json, DirectEditing.class);
if (!directEditing.getCreators().isEmpty()) {
@ -174,7 +178,7 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
FileMenuFilter.isEditorAvailable(getContext().getContentResolver(),
user,
MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN) &&
file != null) {
file != null && !file.isEncrypted()) {
// richWorkspace
// == "": no info set -> show button
// == null: disabled on server side -> hide button

View File

@ -1008,17 +1008,18 @@ public class OCFileListFragment extends ExtendedListFragment implements
.getCapability(account.getAccountName());
if (PreviewMediaFragment.canBePreviewed(file) && account.getServer().getVersion()
.isMediaStreamingSupported()) {
.isMediaStreamingSupported() && !file.isEncrypted()) {
// stream media preview on >= NC14
((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true, true, true);
} else if (FileMenuFilter.isEditorAvailable(requireContext().getContentResolver(),
accountManager.getUser(),
file.getMimeType()) &&
!file.isEncrypted() &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mContainerActivity.getFileOperationsHelper().openFileWithTextEditor(file, getContext());
} else if (capability.getRichDocumentsMimeTypeList().contains(file.getMimeType()) &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
capability.getRichDocumentsDirectEditing().isTrue()) {
capability.getRichDocumentsDirectEditing().isTrue() && !file.isEncrypted()) {
mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext());
} else {
// automatic download, preview on finish

View File

@ -160,8 +160,8 @@ public class SearchShareesFragment extends Fragment implements ShareUserListAdap
// Get Users and Groups
if (((FileActivity) mListener).getStorageManager() != null) {
mShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
mFile.getRemotePath(),
mAccount.name
mFile.getRemotePath(),
mAccount.name
);
// Update list of users/groups

View File

@ -606,8 +606,8 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
if (((FileActivity) mListener).getStorageManager() != null) {
// Get Users and Groups
mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
mFile.getRemotePath(),
mAccount.name
mFile.getRemotePath(),
mAccount.name
);
// Update list of users/groups
@ -672,9 +672,9 @@ public class ShareFileFragment extends Fragment implements ShareUserListAdapter.
} else if (((FileActivity) mListener).getStorageManager() != null) {
// Get public share
mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
mFile.getRemotePath(),
ShareType.PUBLIC_LINK,
""
mFile.getRemotePath(),
ShareType.PUBLIC_LINK,
""
);
// Update public share section

View File

@ -227,7 +227,7 @@ public class FileOperationsHelper {
// check for changed eTag
CheckEtagRemoteOperation checkEtagOperation = new CheckEtagRemoteOperation(file.getRemotePath(),
file.getEtag());
file.getEtag());
RemoteOperationResult result = checkEtagOperation.execute(user.toPlatformAccount(), fileActivity);
// eTag changed, sync file
@ -766,7 +766,7 @@ public class FileOperationsHelper {
sendIntent.setComponent(new ComponentName(packageName, activityName));
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" +
context.getResources().getString(R.string.image_cache_provider_authority) +
file.getRemotePath()));
file.getRemotePath()));
sendIntent.putExtra(Intent.ACTION_SEND, true); // Send Action
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@ -797,7 +797,7 @@ public class FileOperationsHelper {
} else {
uri = Uri.parse(UriUtils.URI_CONTENT_SCHEME +
context.getResources().getString(R.string.image_cache_provider_authority) +
file.getRemotePath());
file.getRemotePath());
}
intent.setDataAndType(uri, file.getMimeType());
@ -863,7 +863,7 @@ public class FileOperationsHelper {
public void toggleEncryption(OCFile file, boolean shouldBeEncrypted) {
if (file.isEncrypted() != shouldBeEncrypted) {
EventBus.getDefault().post(new EncryptionEvent(file.getLocalId(), file.getRemoteId(), file.getRemotePath(),
shouldBeEncrypted));
shouldBeEncrypted));
}
}
@ -894,7 +894,7 @@ public class FileOperationsHelper {
Intent service = new Intent(fileActivity, OperationsService.class);
service.setAction(OperationsService.ACTION_REMOVE);
service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_FILE, file);
service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
service.putExtra(OperationsService.EXTRA_IN_BACKGROUND, inBackground);
mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
@ -906,13 +906,12 @@ public class FileOperationsHelper {
}
public void createFolder(String remotePath, boolean createFullPath) {
public void createFolder(String remotePath) {
// Create Folder
Intent service = new Intent(fileActivity, OperationsService.class);
service.setAction(OperationsService.ACTION_CREATE_FOLDER);
service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment));

View File

@ -53,7 +53,6 @@ import java.util.List;
import javax.inject.Inject;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.LinearLayoutManager;

View File

@ -24,7 +24,9 @@ package com.owncloud.android.utils;
import android.accounts.Account;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Pair;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@ -36,8 +38,14 @@ import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.e2ee.GetMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
import com.owncloud.android.operations.UploadException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.httpclient.HttpStatus;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@ -97,9 +105,9 @@ public final class EncryptionUtils {
public static final String MNEMONIC = "MNEMONIC";
public static final int ivLength = 16;
public static final int saltLength = 40;
public static final String ivDelimiter = "|"; // not base64 encoded
private static final String HASH_DELIMITER = "$";
private static final String ivDelimiter = "fA=="; // "|" base64 encoded
private static final int iterationCount = 1024;
private static final int keyStrength = 256;
private static final String AES_CIPHER = "AES/GCM/NoPadding";
@ -539,7 +547,7 @@ public final class EncryptionUtils {
IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
// split up iv, salt
String[] strings = privateKey.split(ivDelimiter);
String[] strings = privateKey.split("\\" + ivDelimiter);
String realPrivateKey = strings[0];
byte[] iv = decodeStringToBase64Bytes(strings[1]);
byte[] salt = decodeStringToBase64Bytes(strings[2]);
@ -704,4 +712,93 @@ public final class EncryptionUtils {
return hashWithSalt.equals(newHash);
}
public static String lockFolder(OCFile parentFile, OwnCloudClient client) throws UploadException {
// Lock folder
LockFileRemoteOperation lockFileOperation = new LockFileRemoteOperation(parentFile.getLocalId());
RemoteOperationResult lockFileOperationResult = lockFileOperation.execute(client);
if (lockFileOperationResult.isSuccess() &&
!TextUtils.isEmpty((String) lockFileOperationResult.getData().get(0))) {
return (String) lockFileOperationResult.getData().get(0);
} else if (lockFileOperationResult.getHttpCode() == HttpStatus.SC_FORBIDDEN) {
throw new UploadException("Forbidden! Please try again later.)");
} else {
throw new UploadException("Could not lock folder");
}
}
/**
* @param parentFile file metadata should be retrieved for
* @return Pair: boolean: true: metadata already exists, false: metadata new created
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static Pair<Boolean, DecryptedFolderMetadata> retrieveMetadata(OCFile parentFile,
OwnCloudClient client,
String privateKey,
String publicKey) throws UploadException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException,
IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, CertificateException {
GetMetadataRemoteOperation getMetadataOperation = new GetMetadataRemoteOperation(parentFile.getLocalId());
RemoteOperationResult getMetadataOperationResult = getMetadataOperation.execute(client);
DecryptedFolderMetadata metadata;
if (getMetadataOperationResult.isSuccess()) {
// decrypt metadata
String serializedEncryptedMetadata = (String) getMetadataOperationResult.getData().get(0);
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
});
return new Pair<>(Boolean.TRUE, EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey));
} else if (getMetadataOperationResult.getHttpCode() == HttpStatus.SC_NOT_FOUND) {
// new metadata
metadata = new DecryptedFolderMetadata();
metadata.setMetadata(new DecryptedFolderMetadata.Metadata());
metadata.getMetadata().setMetadataKeys(new HashMap<>());
String metadataKey = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.generateKey());
String encryptedMetadataKey = EncryptionUtils.encryptStringAsymmetric(metadataKey, publicKey);
metadata.getMetadata().getMetadataKeys().put(0, encryptedMetadataKey);
return new Pair<>(Boolean.FALSE, metadata);
} else {
// TODO error
throw new UploadException("something wrong");
}
}
public static void uploadMetadata(OCFile parentFile,
String serializedFolderMetadata,
String token,
OwnCloudClient client,
boolean metadataExists) throws UploadException {
RemoteOperationResult uploadMetadataOperationResult;
if (metadataExists) {
// update metadata
UpdateMetadataRemoteOperation storeMetadataOperation = new UpdateMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata, token);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
} else {
// store metadata
StoreMetadataRemoteOperation storeMetadataOperation = new StoreMetadataRemoteOperation(
parentFile.getLocalId(), serializedFolderMetadata);
uploadMetadataOperationResult = storeMetadataOperation.execute(client);
}
if (!uploadMetadataOperationResult.isSuccess()) {
throw new UploadException("Storing/updating metadata was not successful");
}
}
public static RemoteOperationResult unlockFolder(OCFile parentFolder, OwnCloudClient client, String token) {
if (token != null) {
return new UnlockFileRemoteOperation(parentFolder.getLocalId(), token).execute(client);
} else {
return new RemoteOperationResult(new Exception("No token available"));
}
}
}

View File

@ -201,6 +201,7 @@ public final class FileStorageUtils {
*/
public static OCFile fillOCFile(RemoteFile remote) {
OCFile file = new OCFile(remote.getRemotePath());
file.setDecryptedRemotePath(remote.getRemotePath());
file.setCreationTimestamp(remote.getCreationTimestamp());
if (MimeType.DIRECTORY.equalsIgnoreCase(remote.getMimeType())) {
file.setFileLength(remote.getSize());
@ -449,7 +450,7 @@ public final class FileStorageUtils {
return true;
}
while (!OCFile.ROOT_PATH.equals(file.getRemotePath())) {
while (!OCFile.ROOT_PATH.equals(file.getDecryptedRemotePath())) {
if (file.isEncrypted()) {
return true;
}

View File

@ -93,4 +93,4 @@ public class GetShareWithUsersAsyncTask extends AsyncTask<Object, Void, Pair<Rem
}
}
}
}

View File

@ -22,9 +22,11 @@ package com.owncloud.android.utils;
*/
public final class MimeType {
public static final String DIRECTORY = "DIR";
public static final String WEBDAV_FOLDER = "httpd/unix-directory";
public static final String JPEG = "image/jpeg";
public static final String TIFF = "image/tiff";
public static final String TEXT_PLAIN = "text/plain";
public static final String FILE = "application/octet-stream";
private MimeType() {
// No instance

View File

@ -301,7 +301,7 @@ public final class MimeTypeUtil {
*/
public static boolean isImage(ServerFileInterface file) {
return MimeTypeUtil.isImage(file.getMimeType())
|| MimeTypeUtil.isImage(getMimeTypeFromPath(file.getRemotePath()));
|| MimeTypeUtil.isImage(getMimeTypeFromPath(file.getRemotePath()));
}
/**
@ -310,7 +310,7 @@ public final class MimeTypeUtil {
*/
public static boolean isText(OCFile file) {
return MimeTypeUtil.isText(file.getMimeType())
|| MimeTypeUtil.isText(getMimeTypeFromPath(file.getRemotePath()));
|| MimeTypeUtil.isText(getMimeTypeFromPath(file.getRemotePath()));
}
/**