mirror of
https://github.com/nextcloud/android.git
synced 2024-12-20 16:02:01 +03:00
Merge remote-tracking branch 'origin/master' into dev
This commit is contained in:
commit
e2007eb634
29 changed files with 511 additions and 137 deletions
|
@ -26,8 +26,8 @@ import android.text.TextUtils;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.nextcloud.test.RetryTestRule;
|
|
||||||
import com.nextcloud.test.RandomStringGenerator;
|
import com.nextcloud.test.RandomStringGenerator;
|
||||||
|
import com.nextcloud.test.RetryTestRule;
|
||||||
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
|
||||||
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
import com.owncloud.android.datamodel.EncryptedFolderMetadata;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
|
@ -56,6 +56,7 @@ import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -87,6 +88,7 @@ import static com.owncloud.android.utils.EncryptionUtils.verifySHA512;
|
||||||
import static junit.framework.Assert.assertFalse;
|
import static junit.framework.Assert.assertFalse;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class EncryptionTestIT {
|
public class EncryptionTestIT {
|
||||||
|
@ -406,6 +408,58 @@ public class EncryptionTestIT {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filedrop() throws Exception {
|
||||||
|
DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata();
|
||||||
|
|
||||||
|
// add filedrop
|
||||||
|
Map<String, DecryptedFolderMetadata.DecryptedFile> filesdrop = new HashMap<>();
|
||||||
|
|
||||||
|
DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
|
||||||
|
data.setKey("9dfzbIYDt28zTyZfbcll+g==");
|
||||||
|
data.setFilename("test2.txt");
|
||||||
|
data.setVersion(1);
|
||||||
|
|
||||||
|
DecryptedFolderMetadata.DecryptedFile file = new DecryptedFolderMetadata.DecryptedFile();
|
||||||
|
file.setInitializationVector("hnJLF8uhDvDoFK4ajuvwrg==");
|
||||||
|
file.setEncrypted(data);
|
||||||
|
file.setMetadataKey(0);
|
||||||
|
file.setAuthenticationTag("qOQZdu5soFO77Y7y4rAOVA==");
|
||||||
|
|
||||||
|
filesdrop.put("eie8iaeiaes8e87td6", file);
|
||||||
|
|
||||||
|
decryptedFolderMetadata1.setFiledrop(filesdrop);
|
||||||
|
|
||||||
|
// encrypt
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata1 = encryptFolderMetadata(
|
||||||
|
decryptedFolderMetadata1,
|
||||||
|
privateKey
|
||||||
|
);
|
||||||
|
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, cert);
|
||||||
|
|
||||||
|
// serialize
|
||||||
|
String encryptedJson = serializeJSON(encryptedFolderMetadata1);
|
||||||
|
|
||||||
|
// de-serialize
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
|
||||||
|
new TypeToken<EncryptedFolderMetadata>() {
|
||||||
|
});
|
||||||
|
|
||||||
|
// decrypt
|
||||||
|
DecryptedFolderMetadata decryptedFolderMetadata2 = decryptFolderMetaData(
|
||||||
|
encryptedFolderMetadata2, privateKey);
|
||||||
|
|
||||||
|
// compare
|
||||||
|
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1),
|
||||||
|
serializeJSON(decryptedFolderMetadata2)));
|
||||||
|
|
||||||
|
assertEquals(decryptedFolderMetadata1.getFiles().size() + decryptedFolderMetadata1.getFiledrop().size(),
|
||||||
|
decryptedFolderMetadata2.getFiles().size());
|
||||||
|
|
||||||
|
// no filedrop content means null
|
||||||
|
assertNull(decryptedFolderMetadata2.getFiledrop());
|
||||||
|
}
|
||||||
|
|
||||||
private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
|
private void addFile(DecryptedFolderMetadata decryptedFolderMetadata, int counter) {
|
||||||
// Add new file
|
// Add new file
|
||||||
// Always generate new
|
// Always generate new
|
||||||
|
|
|
@ -24,6 +24,8 @@ package com.owncloud.android.datamodel;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypted class representation of metadata json of folder metadata.
|
* Decrypted class representation of metadata json of folder metadata.
|
||||||
*/
|
*/
|
||||||
|
@ -31,6 +33,8 @@ public class DecryptedFolderMetadata {
|
||||||
private Metadata metadata;
|
private Metadata metadata;
|
||||||
private Map<String, DecryptedFile> files;
|
private Map<String, DecryptedFile> files;
|
||||||
|
|
||||||
|
private Map<String, DecryptedFile> filedrop;
|
||||||
|
|
||||||
public DecryptedFolderMetadata() {
|
public DecryptedFolderMetadata() {
|
||||||
this.metadata = new Metadata();
|
this.metadata = new Metadata();
|
||||||
this.files = new HashMap<>();
|
this.files = new HashMap<>();
|
||||||
|
@ -57,6 +61,15 @@ public class DecryptedFolderMetadata {
|
||||||
this.files = files;
|
this.files = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setFiledrop(Map<String, DecryptedFile> filedrop) {
|
||||||
|
this.filedrop = filedrop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, DecryptedFile> getFiledrop() {
|
||||||
|
return filedrop;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Metadata {
|
public static class Metadata {
|
||||||
private Map<Integer, String> metadataKeys; // each keys is encrypted on its own, decrypt on use
|
private Map<Integer, String> metadataKeys; // each keys is encrypted on its own, decrypt on use
|
||||||
private Sharing sharing;
|
private Sharing sharing;
|
||||||
|
|
|
@ -30,9 +30,14 @@ public class EncryptedFolderMetadata {
|
||||||
private DecryptedFolderMetadata.Metadata metadata;
|
private DecryptedFolderMetadata.Metadata metadata;
|
||||||
private Map<String, EncryptedFile> files;
|
private Map<String, EncryptedFile> files;
|
||||||
|
|
||||||
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata, Map<String, EncryptedFile> files) {
|
private Map<String, EncryptedFile> filedrop;
|
||||||
|
|
||||||
|
public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata,
|
||||||
|
Map<String, EncryptedFile> files,
|
||||||
|
Map<String, EncryptedFile> filesdrop) {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
|
this.filedrop = filesdrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecryptedFolderMetadata.Metadata getMetadata() {
|
public DecryptedFolderMetadata.Metadata getMetadata() {
|
||||||
|
@ -40,7 +45,11 @@ public class EncryptedFolderMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, EncryptedFile> getFiles() {
|
public Map<String, EncryptedFile> getFiles() {
|
||||||
return this.files;
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, EncryptedFile> getFiledrop() {
|
||||||
|
return filedrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMetadata(DecryptedFolderMetadata.Metadata metadata) {
|
public void setMetadata(DecryptedFolderMetadata.Metadata metadata) {
|
||||||
|
@ -58,19 +67,19 @@ public class EncryptedFolderMetadata {
|
||||||
private int metadataKey;
|
private int metadataKey;
|
||||||
|
|
||||||
public String getEncrypted() {
|
public String getEncrypted() {
|
||||||
return this.encrypted;
|
return encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getInitializationVector() {
|
public String getInitializationVector() {
|
||||||
return this.initializationVector;
|
return initializationVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthenticationTag() {
|
public String getAuthenticationTag() {
|
||||||
return this.authenticationTag;
|
return authenticationTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMetadataKey() {
|
public int getMetadataKey() {
|
||||||
return this.metadataKey;
|
return metadataKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncrypted(String encrypted) {
|
public void setEncrypted(String encrypted) {
|
||||||
|
|
|
@ -602,7 +602,7 @@ public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterfa
|
||||||
public long getLocalId() {
|
public long getLocalId() {
|
||||||
if (localId > 0) {
|
if (localId > 0) {
|
||||||
return localId;
|
return localId;
|
||||||
} else if (remoteId != null) {
|
} else if (remoteId != null && remoteId.length() > 8) {
|
||||||
return Long.parseLong(remoteId.substring(0, 8).replaceAll("^0*", ""));
|
return Long.parseLong(remoteId.substring(0, 8).replaceAll("^0*", ""));
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class CreateShareViaLinkOperation extends SyncOperation {
|
||||||
|
|
||||||
private String path;
|
private String path;
|
||||||
private String password;
|
private String password;
|
||||||
|
private int permissions = OCShare.NO_PERMISSION;
|
||||||
|
|
||||||
public CreateShareViaLinkOperation(String path, String password, FileDataStorageManager storageManager) {
|
public CreateShareViaLinkOperation(String path, String password, FileDataStorageManager storageManager) {
|
||||||
super(storageManager);
|
super(storageManager);
|
||||||
|
@ -48,6 +49,11 @@ public class CreateShareViaLinkOperation extends SyncOperation {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CreateShareViaLinkOperation(String path, FileDataStorageManager storageManager, int permissions) {
|
||||||
|
this(path, null, storageManager);
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RemoteOperationResult run(OwnCloudClient client) {
|
protected RemoteOperationResult run(OwnCloudClient client) {
|
||||||
CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path,
|
CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path,
|
||||||
|
@ -55,7 +61,7 @@ public class CreateShareViaLinkOperation extends SyncOperation {
|
||||||
"",
|
"",
|
||||||
false,
|
false,
|
||||||
password,
|
password,
|
||||||
OCShare.NO_PERMISSION);
|
permissions);
|
||||||
createOp.setGetShareDetails(true);
|
createOp.setGetShareDetails(true);
|
||||||
RemoteOperationResult result = createOp.execute(client);
|
RemoteOperationResult result = createOp.execute(client);
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,8 @@ public class CreateShareWithShareeOperation extends SyncOperation {
|
||||||
* @param shareeName User or group name of the target sharee.
|
* @param shareeName User or group name of the target sharee.
|
||||||
* @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
|
* @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
|
||||||
* are the only valid values for the moment.
|
* are the only valid values for the moment.
|
||||||
* @param permissions Share permissions key as detailed in https://doc.owncloud.org/server/8.2/developer_manual/core/ocs-share-api.html
|
* @param permissions Share permissions key as detailed in
|
||||||
|
* https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html#create-a-new-share
|
||||||
* .
|
* .
|
||||||
*/
|
*/
|
||||||
public CreateShareWithShareeOperation(String path,
|
public CreateShareWithShareeOperation(String path,
|
||||||
|
|
|
@ -420,13 +420,12 @@ public class RefreshFolderOperation extends RemoteOperation {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void removeLocalFolder() {
|
private void removeLocalFolder() {
|
||||||
if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
|
if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
|
||||||
String currentSavePath = FileStorageUtils.getSavePath(user.getAccountName());
|
String currentSavePath = FileStorageUtils.getSavePath(user.getAccountName());
|
||||||
mStorageManager.removeFolder(
|
mStorageManager.removeFolder(
|
||||||
mLocalFolder,
|
mLocalFolder,
|
||||||
true,
|
true,
|
||||||
mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)
|
mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,13 +156,13 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation {
|
||||||
// return success
|
// return success
|
||||||
return result;
|
return result;
|
||||||
} catch (NoSuchAlgorithmException |
|
} catch (NoSuchAlgorithmException |
|
||||||
IOException |
|
IOException |
|
||||||
InvalidKeyException |
|
InvalidKeyException |
|
||||||
InvalidAlgorithmParameterException |
|
InvalidAlgorithmParameterException |
|
||||||
NoSuchPaddingException |
|
NoSuchPaddingException |
|
||||||
BadPaddingException |
|
BadPaddingException |
|
||||||
IllegalBlockSizeException |
|
IllegalBlockSizeException |
|
||||||
InvalidKeySpecException e) {
|
InvalidKeySpecException e) {
|
||||||
result = new RemoteOperationResult(e);
|
result = new RemoteOperationResult(e);
|
||||||
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e);
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
|
|
||||||
import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation;
|
import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
|
import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
|
||||||
|
@ -419,18 +418,6 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
|
|
||||||
mFile.setParentId(parent.getFileId());
|
mFile.setParentId(parent.getFileId());
|
||||||
|
|
||||||
// try to unlock folder with stored token, e.g. when upload needs to be resumed or app crashed
|
|
||||||
// the parent folder should exist as it is a resume of a broken upload
|
|
||||||
if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
|
|
||||||
UnlockFileRemoteOperation unlockFileOperation = new UnlockFileRemoteOperation(parent.getLocalId(),
|
|
||||||
mFolderUnlockToken);
|
|
||||||
RemoteOperationResult unlockFileOperationResult = unlockFileOperation.execute(client);
|
|
||||||
|
|
||||||
if (!unlockFileOperationResult.isSuccess()) {
|
|
||||||
return unlockFileOperationResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if any parent is encrypted
|
// check if any parent is encrypted
|
||||||
encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
|
encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
|
||||||
mFile.setEncrypted(encryptedAncestor);
|
mFile.setEncrypted(encryptedAncestor);
|
||||||
|
@ -470,10 +457,15 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
}
|
}
|
||||||
/***** E2E *****/
|
/***** E2E *****/
|
||||||
|
|
||||||
token = EncryptionUtils.lockFolder(parentFile, client);
|
// we might have an old token from interrupted upload
|
||||||
// immediately store it
|
if (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()) {
|
||||||
mUpload.setFolderUnlockToken(token);
|
token = mFolderUnlockToken;
|
||||||
uploadsStorageManager.updateUpload(mUpload);
|
} else {
|
||||||
|
token = EncryptionUtils.lockFolder(parentFile, client);
|
||||||
|
// immediately store it
|
||||||
|
mUpload.setFolderUnlockToken(token);
|
||||||
|
uploadsStorageManager.updateUpload(mUpload);
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata
|
// Update metadata
|
||||||
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parentFile,
|
||||||
|
@ -1172,6 +1164,7 @@ public class UploadFileOperation extends SyncOperation {
|
||||||
Log_OC.d(TAG, "Cancelling upload during upload preparations.");
|
Log_OC.d(TAG, "Cancelling upload during upload preparations.");
|
||||||
mCancellationRequested.set(true);
|
mCancellationRequested.set(true);
|
||||||
} else {
|
} else {
|
||||||
|
mCancellationRequested.set(true);
|
||||||
Log_OC.e(TAG, "No upload in progress. This should not happen.");
|
Log_OC.e(TAG, "No upload in progress. This should not happen.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -54,6 +54,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
||||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||||
import com.owncloud.android.lib.resources.files.RestoreFileVersionRemoteOperation;
|
import com.owncloud.android.lib.resources.files.RestoreFileVersionRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.files.model.FileVersion;
|
import com.owncloud.android.lib.resources.files.model.FileVersion;
|
||||||
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||||
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
|
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
|
||||||
import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
|
import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
|
||||||
|
@ -110,6 +111,7 @@ public class OperationsService extends Service {
|
||||||
public static final String EXTRA_IN_BACKGROUND = "IN_BACKGROUND";
|
public static final String EXTRA_IN_BACKGROUND = "IN_BACKGROUND";
|
||||||
|
|
||||||
public static final String ACTION_CREATE_SHARE_VIA_LINK = "CREATE_SHARE_VIA_LINK";
|
public static final String ACTION_CREATE_SHARE_VIA_LINK = "CREATE_SHARE_VIA_LINK";
|
||||||
|
public static final String ACTION_CREATE_SECURE_FILE_DROP = "CREATE_SECURE_FILE_DROP";
|
||||||
public static final String ACTION_CREATE_SHARE_WITH_SHAREE = "CREATE_SHARE_WITH_SHAREE";
|
public static final String ACTION_CREATE_SHARE_WITH_SHAREE = "CREATE_SHARE_WITH_SHAREE";
|
||||||
public static final String ACTION_UNSHARE = "UNSHARE";
|
public static final String ACTION_UNSHARE = "UNSHARE";
|
||||||
public static final String ACTION_UPDATE_PUBLIC_SHARE = "UPDATE_PUBLIC_SHARE";
|
public static final String ACTION_UPDATE_PUBLIC_SHARE = "UPDATE_PUBLIC_SHARE";
|
||||||
|
@ -519,6 +521,13 @@ public class OperationsService extends Service {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ACTION_CREATE_SECURE_FILE_DROP:
|
||||||
|
remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
|
||||||
|
operation = new CreateShareViaLinkOperation(remotePath,
|
||||||
|
fileDataStorageManager,
|
||||||
|
OCShare.CREATE_PERMISSION_FLAG);
|
||||||
|
break;
|
||||||
|
|
||||||
case ACTION_UPDATE_PUBLIC_SHARE:
|
case ACTION_UPDATE_PUBLIC_SHARE:
|
||||||
shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1);
|
shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.nextcloud.client.account.User;
|
||||||
import com.owncloud.android.datamodel.OCFile;
|
import com.owncloud.android.datamodel.OCFile;
|
||||||
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
||||||
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
import com.owncloud.android.ui.fragment.FileDetailSharingFragment;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
@ -34,8 +35,8 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
* File details pager adapter.
|
* File details pager adapter.
|
||||||
*/
|
*/
|
||||||
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
||||||
private OCFile file;
|
private final OCFile file;
|
||||||
private User user;
|
private final User user;
|
||||||
|
|
||||||
private FileDetailSharingFragment fileDetailSharingFragment;
|
private FileDetailSharingFragment fileDetailSharingFragment;
|
||||||
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
|
private FileDetailActivitiesFragment fileDetailActivitiesFragment;
|
||||||
|
@ -71,9 +72,15 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
if (file.isEncrypted()) {
|
if (file.isEncrypted()) {
|
||||||
return 1; // sharing not allowed for encrypted files, thus only show first tab (activities)
|
if (EncryptionUtils.supportsSecureFiledrop(file, user)) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
// sharing not allowed for encrypted files, thus only show first tab (activities)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return 2; // show activities and sharing tab
|
// unencrypted files/folders
|
||||||
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,22 +76,28 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder {
|
||||||
String text = String.format(context.getString(R.string.share_link_with_label), publicShare.getLabel());
|
String text = String.format(context.getString(R.string.share_link_with_label), publicShare.getLabel());
|
||||||
binding.name.setText(text);
|
binding.name.setText(text);
|
||||||
} else {
|
} else {
|
||||||
binding.name.setText(R.string.share_link);
|
if (SharingMenuHelper.isSecureFileDrop(publicShare)) {
|
||||||
|
binding.name.setText(context.getResources().getString(R.string.share_permission_secure_file_drop));
|
||||||
|
} else {
|
||||||
|
binding.name.setText(R.string.share_link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon);
|
viewThemeUtils.platform.colorImageViewBackgroundAndIcon(binding.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
String permissionName = SharingMenuHelper.getPermissionName(context, publicShare);
|
String permissionName = SharingMenuHelper.getPermissionName(context, publicShare);
|
||||||
setPermissionName(permissionName);
|
setPermissionName(publicShare, permissionName);
|
||||||
|
|
||||||
binding.copyLink.setOnClickListener(v -> listener.copyLink(publicShare));
|
binding.copyLink.setOnClickListener(v -> listener.copyLink(publicShare));
|
||||||
binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(publicShare));
|
binding.overflowMenu.setOnClickListener(v -> listener.showSharingMenuActionSheet(publicShare));
|
||||||
binding.shareByLinkContainer.setOnClickListener(v -> listener.showPermissionsDialog(publicShare));
|
if (!SharingMenuHelper.isSecureFileDrop(publicShare)) {
|
||||||
|
binding.shareByLinkContainer.setOnClickListener(v -> listener.showPermissionsDialog(publicShare));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPermissionName(String permissionName) {
|
private void setPermissionName(OCShare publicShare, String permissionName) {
|
||||||
if (!TextUtils.isEmpty(permissionName)) {
|
if (!TextUtils.isEmpty(permissionName) && !SharingMenuHelper.isSecureFileDrop(publicShare)) {
|
||||||
binding.permissionName.setText(permissionName);
|
binding.permissionName.setText(permissionName);
|
||||||
binding.permissionName.setVisibility(View.VISIBLE);
|
binding.permissionName.setVisibility(View.VISIBLE);
|
||||||
viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName);
|
viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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.owncloud.android.ui.adapter
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.owncloud.android.databinding.FileDetailsShareSecureFileDropAddNewItemBinding
|
||||||
|
|
||||||
|
internal class NewSecureFileDropViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private var binding: FileDetailsShareSecureFileDropAddNewItemBinding? = null
|
||||||
|
|
||||||
|
constructor(binding: FileDetailsShareSecureFileDropAddNewItemBinding) : this(binding.root) {
|
||||||
|
this.binding = binding
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(listener: ShareeListAdapterListener) {
|
||||||
|
binding!!.addNewSecureFileDrop.setOnClickListener { v: View? -> listener.createSecureFileDrop() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -206,9 +206,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFavoriteAttributeForItemID(String fileId, boolean favorite, boolean removeFromList) {
|
public void setFavoriteAttributeForItemID(String remotePath, boolean favorite, boolean removeFromList) {
|
||||||
for (OCFile file : mFiles) {
|
for (OCFile file : mFiles) {
|
||||||
if (file.getRemoteId().equals(fileId)) {
|
if (file.getRemotePath().equals(remotePath)) {
|
||||||
file.setFavorite(favorite);
|
file.setFavorite(favorite);
|
||||||
|
|
||||||
if (removeFromList) {
|
if (removeFromList) {
|
||||||
|
@ -220,7 +220,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
}
|
}
|
||||||
|
|
||||||
for (OCFile file : mFilesAll) {
|
for (OCFile file : mFilesAll) {
|
||||||
if (file.getRemoteId().equals(fileId)) {
|
if (file.getRemotePath().equals(remotePath)) {
|
||||||
file.setFavorite(favorite);
|
file.setFavorite(favorite);
|
||||||
|
|
||||||
mStorageManager.saveFile(file);
|
mStorageManager.saveFile(file);
|
||||||
|
@ -234,7 +234,13 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSortOrder sortOrder = preferences.getSortOrderByFolder(currentDirectory);
|
FileSortOrder sortOrder = preferences.getSortOrderByFolder(currentDirectory);
|
||||||
mFiles = sortOrder.sortCloudFiles(mFiles);
|
if (searchType == SearchType.SHARED_FILTER) {
|
||||||
|
Collections.sort(mFiles,
|
||||||
|
(o1, o2) -> Long.compare(o2.getFirstShareTimestamp(), o1.getFirstShareTimestamp())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mFiles = sortOrder.sortCloudFiles(mFiles);
|
||||||
|
}
|
||||||
|
|
||||||
new Handler(Looper.getMainLooper()).post(this::notifyDataSetChanged);
|
new Handler(Looper.getMainLooper()).post(this::notifyDataSetChanged);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import com.owncloud.android.ui.fragment.SearchType
|
||||||
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
|
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface
|
||||||
import com.owncloud.android.utils.BitmapUtils
|
import com.owncloud.android.utils.BitmapUtils
|
||||||
import com.owncloud.android.utils.DisplayUtils
|
import com.owncloud.android.utils.DisplayUtils
|
||||||
|
import com.owncloud.android.utils.EncryptionUtils
|
||||||
import com.owncloud.android.utils.MimeTypeUtil
|
import com.owncloud.android.utils.MimeTypeUtil
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||||
|
|
||||||
|
@ -237,7 +238,11 @@ class OCFileListDelegate(
|
||||||
bindGridMetadataViews(file, gridViewHolder)
|
bindGridMetadataViews(file, gridViewHolder)
|
||||||
|
|
||||||
// shares
|
// shares
|
||||||
val shouldHideShare = gridView || hideItemOptions || file.isFolder && !file.canReshare() || file.isEncrypted ||
|
val shouldHideShare = gridView ||
|
||||||
|
hideItemOptions ||
|
||||||
|
file.isFolder && !file.canReshare() ||
|
||||||
|
!file.isFolder && file.isEncrypted ||
|
||||||
|
file.isEncrypted && !EncryptionUtils.supportsSecureFiledrop(file, user) ||
|
||||||
searchType == SearchType.FAVORITE_SEARCH
|
searchType == SearchType.FAVORITE_SEARCH
|
||||||
if (shouldHideShare) {
|
if (shouldHideShare) {
|
||||||
gridViewHolder.shared.visibility = View.GONE
|
gridViewHolder.shared.visibility = View.GONE
|
||||||
|
|
|
@ -35,6 +35,7 @@ import com.owncloud.android.R;
|
||||||
import com.owncloud.android.databinding.FileDetailsShareInternalShareLinkBinding;
|
import com.owncloud.android.databinding.FileDetailsShareInternalShareLinkBinding;
|
||||||
import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding;
|
import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding;
|
||||||
import com.owncloud.android.databinding.FileDetailsSharePublicLinkAddNewItemBinding;
|
import com.owncloud.android.databinding.FileDetailsSharePublicLinkAddNewItemBinding;
|
||||||
|
import com.owncloud.android.databinding.FileDetailsShareSecureFileDropAddNewItemBinding;
|
||||||
import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
|
import com.owncloud.android.databinding.FileDetailsShareShareItemBinding;
|
||||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||||
|
@ -62,19 +63,22 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
private final String userId;
|
private final String userId;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final ViewThemeUtils viewThemeUtils;
|
private final ViewThemeUtils viewThemeUtils;
|
||||||
|
private final boolean encrypted;
|
||||||
|
|
||||||
public ShareeListAdapter(FileActivity fileActivity,
|
public ShareeListAdapter(FileActivity fileActivity,
|
||||||
List<OCShare> shares,
|
List<OCShare> shares,
|
||||||
ShareeListAdapterListener listener,
|
ShareeListAdapterListener listener,
|
||||||
String userId,
|
String userId,
|
||||||
User user,
|
User user,
|
||||||
final ViewThemeUtils viewThemeUtils) {
|
final ViewThemeUtils viewThemeUtils,
|
||||||
|
boolean encrypted) {
|
||||||
this.fileActivity = fileActivity;
|
this.fileActivity = fileActivity;
|
||||||
this.shares = shares;
|
this.shares = shares;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.viewThemeUtils = viewThemeUtils;
|
this.viewThemeUtils = viewThemeUtils;
|
||||||
|
this.encrypted = encrypted;
|
||||||
|
|
||||||
avatarRadiusDimension = fileActivity.getResources().getDimension(R.dimen.user_icon_radius);
|
avatarRadiusDimension = fileActivity.getResources().getDimension(R.dimen.user_icon_radius);
|
||||||
|
|
||||||
|
@ -99,11 +103,19 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
fileActivity,
|
fileActivity,
|
||||||
viewThemeUtils);
|
viewThemeUtils);
|
||||||
case NEW_PUBLIC_LINK:
|
case NEW_PUBLIC_LINK:
|
||||||
return new NewLinkShareViewHolder(
|
if (encrypted) {
|
||||||
FileDetailsSharePublicLinkAddNewItemBinding.inflate(LayoutInflater.from(fileActivity),
|
return new NewSecureFileDropViewHolder(
|
||||||
parent,
|
FileDetailsShareSecureFileDropAddNewItemBinding.inflate(LayoutInflater.from(fileActivity),
|
||||||
false)
|
parent,
|
||||||
);
|
false)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return new NewLinkShareViewHolder(
|
||||||
|
FileDetailsSharePublicLinkAddNewItemBinding.inflate(LayoutInflater.from(fileActivity),
|
||||||
|
parent,
|
||||||
|
false)
|
||||||
|
);
|
||||||
|
}
|
||||||
case INTERNAL:
|
case INTERNAL:
|
||||||
return new InternalShareViewHolder(
|
return new InternalShareViewHolder(
|
||||||
FileDetailsShareInternalShareLinkBinding.inflate(LayoutInflater.from(fileActivity), parent, false),
|
FileDetailsShareInternalShareLinkBinding.inflate(LayoutInflater.from(fileActivity), parent, false),
|
||||||
|
@ -135,6 +147,9 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
} else if (holder instanceof NewLinkShareViewHolder) {
|
} else if (holder instanceof NewLinkShareViewHolder) {
|
||||||
NewLinkShareViewHolder newLinkShareViewHolder = (NewLinkShareViewHolder) holder;
|
NewLinkShareViewHolder newLinkShareViewHolder = (NewLinkShareViewHolder) holder;
|
||||||
newLinkShareViewHolder.bind(listener);
|
newLinkShareViewHolder.bind(listener);
|
||||||
|
} else if (holder instanceof NewSecureFileDropViewHolder) {
|
||||||
|
NewSecureFileDropViewHolder newSecureFileDropViewHolder = (NewSecureFileDropViewHolder) holder;
|
||||||
|
newSecureFileDropViewHolder.bind(listener);
|
||||||
} else {
|
} else {
|
||||||
ShareViewHolder userViewHolder = (ShareViewHolder) holder;
|
ShareViewHolder userViewHolder = (ShareViewHolder) holder;
|
||||||
userViewHolder.bind(share, listener, this, userId, avatarRadiusDimension);
|
userViewHolder.bind(share, listener, this, userId, avatarRadiusDimension);
|
||||||
|
@ -202,9 +217,11 @@ public class ShareeListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
|
||||||
shares.addAll(users);
|
shares.addAll(users);
|
||||||
|
|
||||||
// add internal share link at end
|
// add internal share link at end
|
||||||
final OCShare ocShare = new OCShare();
|
if (!encrypted) {
|
||||||
ocShare.setShareType(ShareType.INTERNAL);
|
final OCShare ocShare = new OCShare();
|
||||||
shares.add(ocShare);
|
ocShare.setShareType(ShareType.INTERNAL);
|
||||||
|
shares.add(ocShare);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OCShare> getShares() {
|
public List<OCShare> getShares() {
|
||||||
|
|
|
@ -36,6 +36,8 @@ public interface ShareeListAdapterListener {
|
||||||
|
|
||||||
void createPublicShareLink();
|
void createPublicShareLink();
|
||||||
|
|
||||||
|
void createSecureFileDrop();
|
||||||
|
|
||||||
void requestPasswordForShare(OCShare share, boolean askForPassword);
|
void requestPasswordForShare(OCShare share, boolean askForPassword);
|
||||||
|
|
||||||
void showPermissionsDialog(OCShare share);
|
void showPermissionsDialog(OCShare share);
|
||||||
|
|
|
@ -25,11 +25,9 @@ package com.owncloud.android.ui.events;
|
||||||
public class FavoriteEvent {
|
public class FavoriteEvent {
|
||||||
public final String remotePath;
|
public final String remotePath;
|
||||||
public final boolean shouldFavorite;
|
public final boolean shouldFavorite;
|
||||||
public final String remoteId;
|
|
||||||
|
|
||||||
public FavoriteEvent(String remotePath, boolean shouldFavorite, String remoteId) {
|
public FavoriteEvent(String remotePath, boolean shouldFavorite) {
|
||||||
this.remotePath = remotePath;
|
this.remotePath = remotePath;
|
||||||
this.shouldFavorite = shouldFavorite;
|
this.shouldFavorite = shouldFavorite;
|
||||||
this.remoteId = remoteId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
|
||||||
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
|
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
|
||||||
import com.owncloud.android.ui.events.FavoriteEvent;
|
import com.owncloud.android.ui.events.FavoriteEvent;
|
||||||
import com.owncloud.android.utils.DisplayUtils;
|
import com.owncloud.android.utils.DisplayUtils;
|
||||||
|
import com.owncloud.android.utils.EncryptionUtils;
|
||||||
import com.owncloud.android.utils.MimeTypeUtil;
|
import com.owncloud.android.utils.MimeTypeUtil;
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||||
|
|
||||||
|
@ -272,12 +273,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
|
||||||
binding.tabLayout.removeAllTabs();
|
binding.tabLayout.removeAllTabs();
|
||||||
|
|
||||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.drawer_item_activities).setIcon(R.drawable.ic_activity));
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.drawer_item_activities).setIcon(R.drawable.ic_activity));
|
||||||
viewThemeUtils.material.themeTabLayout(binding.tabLayout);
|
|
||||||
|
|
||||||
if (!getFile().isEncrypted()) {
|
|
||||||
|
if (!getFile().isEncrypted() || EncryptionUtils.supportsSecureFiledrop(getFile(), user)) {
|
||||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.shared_via_users));
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText(R.string.share_dialog_title).setIcon(R.drawable.shared_via_users));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewThemeUtils.material.themeTabLayout(binding.tabLayout);
|
||||||
|
|
||||||
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(), getFile(), user);
|
final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(), getFile(), user);
|
||||||
binding.pager.setAdapter(adapter);
|
binding.pager.setAdapter(adapter);
|
||||||
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
|
binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) {
|
||||||
|
|
|
@ -163,7 +163,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
this,
|
this,
|
||||||
userId,
|
userId,
|
||||||
user,
|
user,
|
||||||
viewThemeUtils));
|
viewThemeUtils,
|
||||||
|
file.isEncrypted()));
|
||||||
binding.sharesList.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.sharesList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|
||||||
setupView();
|
setupView();
|
||||||
|
@ -193,18 +194,22 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
private void setupView() {
|
private void setupView() {
|
||||||
setShareWithYou();
|
setShareWithYou();
|
||||||
|
|
||||||
FileDetailSharingFragmentHelper.setupSearchView(
|
if (file.isEncrypted()) {
|
||||||
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
binding.searchContainer.setVisibility(View.GONE);
|
||||||
binding.searchView,
|
|
||||||
fileActivity.getComponentName());
|
|
||||||
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
|
||||||
|
|
||||||
if (file.canReshare()) {
|
|
||||||
binding.searchView.setQueryHint(getResources().getString(R.string.share_search));
|
|
||||||
} else {
|
} else {
|
||||||
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
FileDetailSharingFragmentHelper.setupSearchView(
|
||||||
binding.searchView.setInputType(InputType.TYPE_NULL);
|
(SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE),
|
||||||
disableSearchView(binding.searchView);
|
binding.searchView,
|
||||||
|
fileActivity.getComponentName());
|
||||||
|
viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView);
|
||||||
|
|
||||||
|
if (file.canReshare()) {
|
||||||
|
binding.searchView.setQueryHint(getResources().getString(R.string.share_search));
|
||||||
|
} else {
|
||||||
|
binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
|
||||||
|
binding.searchView.setInputType(InputType.TYPE_NULL);
|
||||||
|
disableSearchView(binding.searchView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +282,11 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createSecureFileDrop() {
|
||||||
|
fileOperationsHelper.shareFolderViaSecureFileDrop(file);
|
||||||
|
}
|
||||||
|
|
||||||
private void showSendLinkTo(OCShare publicShare) {
|
private void showSendLinkTo(OCShare publicShare) {
|
||||||
if (file.isSharedViaLink()) {
|
if (file.isSharedViaLink()) {
|
||||||
if (TextUtils.isEmpty(publicShare.getShareLink())) {
|
if (TextUtils.isEmpty(publicShare.getShareLink())) {
|
||||||
|
@ -425,6 +435,19 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
|
||||||
ShareType.PUBLIC_LINK,
|
ShareType.PUBLIC_LINK,
|
||||||
"");
|
"");
|
||||||
|
|
||||||
|
//
|
||||||
|
// boolean supportsSecureFiledrop = file.isEncrypted() &&
|
||||||
|
// capabilities.getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
|
||||||
|
//
|
||||||
|
// if (publicShares.isEmpty() &&
|
||||||
|
// containsNoNewPublicShare(adapter.getShares()) &&
|
||||||
|
// (!file.isEncrypted() || supportsSecureFiledrop)) {
|
||||||
|
// final OCShare ocShare = new OCShare();
|
||||||
|
// ocShare.setShareType(ShareType.NEW_PUBLIC_LINK);
|
||||||
|
// publicShares.add(ocShare);
|
||||||
|
// } else {
|
||||||
|
// adapter.removeNewPublicShare();
|
||||||
|
// }
|
||||||
|
|
||||||
if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) {
|
if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) {
|
||||||
final OCShare ocShare = new OCShare();
|
final OCShare ocShare = new OCShare();
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.owncloud.android.databinding.FileDetailsSharingMenuBottomSheetFragmen
|
||||||
import com.owncloud.android.lib.resources.shares.OCShare;
|
import com.owncloud.android.lib.resources.shares.OCShare;
|
||||||
import com.owncloud.android.lib.resources.shares.ShareType;
|
import com.owncloud.android.lib.resources.shares.ShareType;
|
||||||
import com.owncloud.android.ui.activity.FileActivity;
|
import com.owncloud.android.ui.activity.FileActivity;
|
||||||
|
import com.owncloud.android.ui.fragment.util.SharingMenuHelper;
|
||||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +42,6 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog {
|
||||||
private final FileDetailsSharingMenuBottomSheetActions actions;
|
private final FileDetailsSharingMenuBottomSheetActions actions;
|
||||||
private final OCShare ocShare;
|
private final OCShare ocShare;
|
||||||
private final ViewThemeUtils viewThemeUtils;
|
private final ViewThemeUtils viewThemeUtils;
|
||||||
|
|
||||||
public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity,
|
public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity,
|
||||||
FileDetailsSharingMenuBottomSheetActions actions,
|
FileDetailsSharingMenuBottomSheetActions actions,
|
||||||
OCShare ocShare,
|
OCShare ocShare,
|
||||||
|
@ -88,6 +88,11 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog {
|
||||||
binding.menuShareAddAnotherLink.setVisibility(View.GONE);
|
binding.menuShareAddAnotherLink.setVisibility(View.GONE);
|
||||||
binding.menuShareSendLink.setVisibility(View.GONE);
|
binding.menuShareSendLink.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SharingMenuHelper.isSecureFileDrop(ocShare)) {
|
||||||
|
binding.menuShareAdvancedPermissions.setVisibility(View.GONE);
|
||||||
|
binding.menuShareAddAnotherLink.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupClickListener() {
|
private void setupClickListener() {
|
||||||
|
|
|
@ -1605,7 +1605,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
|
||||||
|
|
||||||
if (remoteOperationResult.isSuccess()) {
|
if (remoteOperationResult.isSuccess()) {
|
||||||
boolean removeFromList = currentSearchType == SearchType.FAVORITE_SEARCH && !event.shouldFavorite;
|
boolean removeFromList = currentSearchType == SearchType.FAVORITE_SEARCH && !event.shouldFavorite;
|
||||||
mAdapter.setFavoriteAttributeForItemID(event.remoteId, event.shouldFavorite, removeFromList);
|
mAdapter.setFavoriteAttributeForItemID(event.remotePath, event.shouldFavorite, removeFromList);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ClientFactory.CreationException e) {
|
} catch (ClientFactory.CreationException e) {
|
||||||
|
|
|
@ -122,11 +122,21 @@ public final class SharingMenuHelper {
|
||||||
return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG;
|
return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isSecureFileDrop(OCShare share) {
|
||||||
|
if (share.getPermissions() == NO_PERMISSION) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (share.getPermissions() & ~SHARE_PERMISSION_FLAG) == CREATE_PERMISSION_FLAG + READ_PERMISSION_FLAG;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getPermissionName(Context context, OCShare share) {
|
public static String getPermissionName(Context context, OCShare share) {
|
||||||
if (SharingMenuHelper.isUploadAndEditingAllowed(share)) {
|
if (SharingMenuHelper.isUploadAndEditingAllowed(share)) {
|
||||||
return context.getResources().getString(R.string.share_permission_can_edit);
|
return context.getResources().getString(R.string.share_permission_can_edit);
|
||||||
} else if (SharingMenuHelper.isReadOnly(share)) {
|
} else if (SharingMenuHelper.isReadOnly(share)) {
|
||||||
return context.getResources().getString(R.string.share_permission_view_only);
|
return context.getResources().getString(R.string.share_permission_view_only);
|
||||||
|
} else if (SharingMenuHelper.isSecureFileDrop(share)) {
|
||||||
|
return context.getResources().getString(R.string.share_permission_secure_file_drop);
|
||||||
} else if (SharingMenuHelper.isFileDrop(share)) {
|
} else if (SharingMenuHelper.isFileDrop(share)) {
|
||||||
return context.getResources().getString(R.string.share_permission_file_drop);
|
return context.getResources().getString(R.string.share_permission_file_drop);
|
||||||
}
|
}
|
||||||
|
|
|
@ -500,6 +500,15 @@ public class FileOperationsHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shareFolderViaSecureFileDrop(@NonNull OCFile file) {
|
||||||
|
fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment));
|
||||||
|
Intent service = new Intent(fileActivity, OperationsService.class);
|
||||||
|
service.setAction(OperationsService.ACTION_CREATE_SECURE_FILE_DROP);
|
||||||
|
service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount());
|
||||||
|
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
|
||||||
|
mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service);
|
||||||
|
}
|
||||||
|
|
||||||
public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) {
|
public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) {
|
||||||
List<OCShare> shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(),
|
List<OCShare> shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(),
|
||||||
ShareType.PUBLIC_LINK,
|
ShareType.PUBLIC_LINK,
|
||||||
|
@ -906,7 +915,7 @@ public class FileOperationsHelper {
|
||||||
|
|
||||||
public void toggleFavoriteFile(OCFile file, boolean shouldBeFavorite) {
|
public void toggleFavoriteFile(OCFile file, boolean shouldBeFavorite) {
|
||||||
if (file.isFavorite() != shouldBeFavorite) {
|
if (file.isFavorite() != shouldBeFavorite) {
|
||||||
EventBus.getDefault().post(new FavoriteEvent(file.getRemotePath(), shouldBeFavorite, file.getRemoteId()));
|
EventBus.getDefault().post(new FavoriteEvent(file.getRemotePath(), shouldBeFavorite));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ import com.owncloud.android.lib.resources.e2ee.LockFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
|
import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
|
import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
|
||||||
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
|
import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
|
||||||
|
import com.owncloud.android.lib.resources.status.NextcloudVersion;
|
||||||
import com.owncloud.android.operations.UploadException;
|
import com.owncloud.android.operations.UploadException;
|
||||||
|
|
||||||
import org.apache.commons.httpclient.HttpStatus;
|
import org.apache.commons.httpclient.HttpStatus;
|
||||||
|
@ -147,18 +148,22 @@ public final class EncryptionUtils {
|
||||||
* @return EncryptedFolderMetadata encrypted folder metadata
|
* @return EncryptedFolderMetadata encrypted folder metadata
|
||||||
*/
|
*/
|
||||||
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
|
public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
|
||||||
String privateKey)
|
String privateKey
|
||||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
)
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||||
IllegalBlockSizeException, InvalidKeySpecException {
|
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||||
|
IllegalBlockSizeException, InvalidKeySpecException {
|
||||||
|
|
||||||
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
|
HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
|
||||||
|
HashMap<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = new HashMap<>();
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
|
EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
|
||||||
.getMetadata(), files);
|
.getMetadata(),
|
||||||
|
files,
|
||||||
|
filesdrop);
|
||||||
|
|
||||||
// Encrypt each file in "files"
|
// Encrypt each file in "files"
|
||||||
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||||
.getFiles().entrySet()) {
|
.getFiles().entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
@ -168,8 +173,8 @@ public final class EncryptionUtils {
|
||||||
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
|
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric(
|
||||||
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||||
privateKey));
|
privateKey));
|
||||||
|
|
||||||
// encrypt
|
// encrypt
|
||||||
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||||
|
@ -181,21 +186,42 @@ public final class EncryptionUtils {
|
||||||
return encryptedFolderMetadata;
|
return encryptedFolderMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException {
|
||||||
|
final Map<String, EncryptedFolderMetadata.EncryptedFile> filesdrop = encryptedFolderMetadata.getFiledrop();
|
||||||
|
for (Map.Entry<String, DecryptedFolderMetadata.DecryptedFile> entry : decryptedFolderMetadata
|
||||||
|
.getFiledrop().entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
EncryptedFolderMetadata.EncryptedFile encryptedFile = new EncryptedFolderMetadata.EncryptedFile();
|
||||||
|
encryptedFile.setInitializationVector(decryptedFile.getInitializationVector());
|
||||||
|
encryptedFile.setMetadataKey(decryptedFile.getMetadataKey());
|
||||||
|
encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
|
// encrypt
|
||||||
|
String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted());
|
||||||
|
encryptedFile.setEncrypted(EncryptionUtils.encryptStringAsymmetric(dataJson, cert));
|
||||||
|
|
||||||
|
filesdrop.put(key, encryptedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* decrypt folder metaData with private key
|
* decrypt folder metaData with private key
|
||||||
*/
|
*/
|
||||||
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
|
public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
|
||||||
String privateKey)
|
String privateKey)
|
||||||
throws NoSuchAlgorithmException, InvalidKeyException,
|
throws NoSuchAlgorithmException, InvalidKeyException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
|
||||||
IllegalBlockSizeException, InvalidKeySpecException {
|
IllegalBlockSizeException, InvalidKeySpecException {
|
||||||
|
|
||||||
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
|
||||||
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
|
||||||
encryptedFolderMetadata.getMetadata(), files);
|
encryptedFolderMetadata.getMetadata(), files);
|
||||||
|
|
||||||
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : encryptedFolderMetadata
|
||||||
.getFiles().entrySet()) {
|
.getFiles().entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
@ -205,18 +231,45 @@ public final class EncryptionUtils {
|
||||||
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(
|
||||||
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata()
|
||||||
.getMetadataKeys().get(encryptedFile.getMetadataKey()), privateKey));
|
.getMetadataKeys().get(encryptedFile.getMetadataKey()),
|
||||||
|
privateKey));
|
||||||
|
|
||||||
// decrypt
|
// decrypt
|
||||||
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey);
|
||||||
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
||||||
new TypeToken<DecryptedFolderMetadata.Data>() {
|
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
files.put(key, decryptedFile);
|
files.put(key, decryptedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, EncryptedFolderMetadata.EncryptedFile> fileDrop = encryptedFolderMetadata.getFiledrop();
|
||||||
|
|
||||||
|
if (fileDrop != null) {
|
||||||
|
for (Map.Entry<String, EncryptedFolderMetadata.EncryptedFile> entry : fileDrop.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue();
|
||||||
|
|
||||||
|
DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
|
||||||
|
decryptedFile.setInitializationVector(encryptedFile.getInitializationVector());
|
||||||
|
decryptedFile.setMetadataKey(encryptedFile.getMetadataKey());
|
||||||
|
decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag());
|
||||||
|
|
||||||
|
// decrypt
|
||||||
|
String dataJson = EncryptionUtils.decryptStringAsymmetric(encryptedFile.getEncrypted(), privateKey);
|
||||||
|
|
||||||
|
decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson,
|
||||||
|
new TypeToken<DecryptedFolderMetadata.Data>() {
|
||||||
|
}));
|
||||||
|
|
||||||
|
files.put(key, decryptedFile);
|
||||||
|
|
||||||
|
// remove from filedrop
|
||||||
|
fileDrop.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return decryptedFolderMetadata;
|
return decryptedFolderMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,8 +279,10 @@ public final class EncryptionUtils {
|
||||||
* @return decrypted metadata or null
|
* @return decrypted metadata or null
|
||||||
*/
|
*/
|
||||||
public static @Nullable
|
public static @Nullable
|
||||||
DecryptedFolderMetadata downloadFolderMetadata(OCFile folder, OwnCloudClient client,
|
DecryptedFolderMetadata downloadFolderMetadata(OCFile folder,
|
||||||
Context context, User user) {
|
OwnCloudClient client,
|
||||||
|
Context context,
|
||||||
|
User user) {
|
||||||
RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
|
RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId())
|
||||||
.execute(client);
|
.execute(client);
|
||||||
|
|
||||||
|
@ -241,11 +296,48 @@ public final class EncryptionUtils {
|
||||||
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
|
||||||
|
|
||||||
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON(
|
||||||
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
serializedEncryptedMetadata, new TypeToken<EncryptedFolderMetadata>() {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return EncryptionUtils.decryptFolderMetaData(encryptedFolderMetadata, privateKey);
|
int filesDropCountBefore = 0;
|
||||||
|
if (encryptedFolderMetadata.getFiledrop() != null) {
|
||||||
|
filesDropCountBefore = encryptedFolderMetadata.getFiledrop().size();
|
||||||
|
}
|
||||||
|
DecryptedFolderMetadata decryptedFolderMetadata = EncryptionUtils.decryptFolderMetaData(
|
||||||
|
encryptedFolderMetadata,
|
||||||
|
privateKey);
|
||||||
|
|
||||||
|
boolean transferredFiledrop = filesDropCountBefore > 0 && decryptedFolderMetadata.getFiles().size() ==
|
||||||
|
encryptedFolderMetadata.getFiles().size() + filesDropCountBefore;
|
||||||
|
|
||||||
|
if (transferredFiledrop) {
|
||||||
|
// lock folder
|
||||||
|
String token = EncryptionUtils.lockFolder(folder, client);
|
||||||
|
|
||||||
|
// upload metadata
|
||||||
|
EncryptedFolderMetadata encryptedFolderMetadataNew = encryptFolderMetadata(decryptedFolderMetadata,
|
||||||
|
privateKey);
|
||||||
|
|
||||||
|
String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadataNew);
|
||||||
|
|
||||||
|
EncryptionUtils.uploadMetadata(folder,
|
||||||
|
serializedFolderMetadata,
|
||||||
|
token,
|
||||||
|
client,
|
||||||
|
true);
|
||||||
|
|
||||||
|
// unlock folder
|
||||||
|
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
|
||||||
|
|
||||||
|
if (!unlockFolderResult.isSuccess()) {
|
||||||
|
Log_OC.e(TAG, unlockFolderResult.getMessage());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedFolderMetadata;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log_OC.e(TAG, e.getMessage());
|
Log_OC.e(TAG, e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
|
@ -287,13 +379,14 @@ public final class EncryptionUtils {
|
||||||
/**
|
/**
|
||||||
* @param ocFile file do crypt
|
* @param ocFile file do crypt
|
||||||
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
||||||
* @param iv initialization vector, either from metadata or {@link EncryptionUtils#randomBytes(int)}
|
* @param iv initialization vector, either from metadata or
|
||||||
|
* {@link EncryptionUtils#randomBytes(int)}
|
||||||
* @return encryptedFile with encryptedBytes and authenticationTag
|
* @return encryptedFile with encryptedBytes and authenticationTag
|
||||||
*/
|
*/
|
||||||
public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
|
public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||||
File file = new File(ocFile.getStoragePath());
|
File file = new File(ocFile.getStoragePath());
|
||||||
|
|
||||||
return encryptFile(file, encryptionKeyBytes, iv);
|
return encryptFile(file, encryptionKeyBytes, iv);
|
||||||
|
@ -302,13 +395,14 @@ public final class EncryptionUtils {
|
||||||
/**
|
/**
|
||||||
* @param file file do crypt
|
* @param file file do crypt
|
||||||
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
* @param encryptionKeyBytes key, either from metadata or {@link EncryptionUtils#generateKey()}
|
||||||
* @param iv initialization vector, either from metadata or {@link EncryptionUtils#randomBytes(int)}
|
* @param iv initialization vector, either from metadata or
|
||||||
|
* {@link EncryptionUtils#randomBytes(int)}
|
||||||
* @return encryptedFile with encryptedBytes and authenticationTag
|
* @return encryptedFile with encryptedBytes and authenticationTag
|
||||||
*/
|
*/
|
||||||
public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
|
public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
|
||||||
|
@ -323,7 +417,7 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
byte[] cryptedBytes = cipher.doFinal(fileBytes);
|
byte[] cryptedBytes = cipher.doFinal(fileBytes);
|
||||||
String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
|
String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes,
|
||||||
cryptedBytes.length - (128 / 8), cryptedBytes.length));
|
cryptedBytes.length - (128 / 8), cryptedBytes.length));
|
||||||
|
|
||||||
return new EncryptedFile(cryptedBytes, authenticationTag);
|
return new EncryptedFile(cryptedBytes, authenticationTag);
|
||||||
}
|
}
|
||||||
|
@ -336,9 +430,9 @@ public final class EncryptionUtils {
|
||||||
* @return decrypted byte[]
|
* @return decrypted byte[]
|
||||||
*/
|
*/
|
||||||
public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
|
public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException, IOException {
|
BadPaddingException, IllegalBlockSizeException, IOException {
|
||||||
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
@ -352,7 +446,7 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
// check authentication tag
|
// check authentication tag
|
||||||
byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
|
byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes,
|
||||||
fileBytes.length - (128 / 8), fileBytes.length);
|
fileBytes.length - (128 / 8), fileBytes.length);
|
||||||
|
|
||||||
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
|
if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
|
||||||
throw new SecurityException("Tag not correct");
|
throw new SecurityException("Tag not correct");
|
||||||
|
@ -372,16 +466,16 @@ public final class EncryptionUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
|
* Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
|
||||||
* Asymmetric encryption, with private and public key
|
* and public key
|
||||||
*
|
*
|
||||||
* @param string String to encrypt
|
* @param string String to encrypt
|
||||||
* @param cert contains public key in it
|
* @param cert contains public key in it
|
||||||
* @return encrypted string
|
* @return encrypted string
|
||||||
*/
|
*/
|
||||||
public static String encryptStringAsymmetric(String string, String cert)
|
public static String encryptStringAsymmetric(String string, String cert)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
NoSuchPaddingException, InvalidKeyException,
|
NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException,
|
BadPaddingException, IllegalBlockSizeException,
|
||||||
CertificateException {
|
CertificateException {
|
||||||
|
|
||||||
|
@ -418,17 +512,17 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
|
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
|
||||||
* Asymmetric encryption, with private and public key
|
* and public key
|
||||||
*
|
*
|
||||||
* @param string string to decrypt
|
* @param string string to decrypt
|
||||||
* @param privateKeyString private key
|
* @param privateKeyString private key
|
||||||
* @return decrypted string
|
* @return decrypted string
|
||||||
*/
|
*/
|
||||||
public static String decryptStringAsymmetric(String string, String privateKeyString)
|
public static String decryptStringAsymmetric(String string, String privateKeyString)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
NoSuchPaddingException, InvalidKeyException,
|
NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException,
|
BadPaddingException, IllegalBlockSizeException,
|
||||||
InvalidKeySpecException {
|
InvalidKeySpecException {
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
|
Cipher cipher = Cipher.getInstance(RSA_CIPHER);
|
||||||
|
@ -513,17 +607,17 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding
|
* Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private
|
||||||
* Asymmetric encryption, with private and public key
|
* and public key
|
||||||
*
|
*
|
||||||
* @param string string to decrypt
|
* @param string string to decrypt
|
||||||
* @param encryptionKeyBytes key from metadata
|
* @param encryptionKeyBytes key from metadata
|
||||||
* @return decrypted string
|
* @return decrypted string
|
||||||
*/
|
*/
|
||||||
public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
|
public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
|
||||||
throws NoSuchAlgorithmException,
|
throws NoSuchAlgorithmException,
|
||||||
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
|
||||||
BadPaddingException, IllegalBlockSizeException {
|
BadPaddingException, IllegalBlockSizeException {
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
Cipher cipher = Cipher.getInstance(AES_CIPHER);
|
||||||
|
|
||||||
|
@ -557,8 +651,8 @@ public final class EncryptionUtils {
|
||||||
* Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
|
* Encrypt private key with symmetric AES encryption, GCM mode mode and no padding
|
||||||
*
|
*
|
||||||
* @param privateKey byte64 encoded string representation of private key
|
* @param privateKey byte64 encoded string representation of private key
|
||||||
* @param keyPhrase key used for encryption, e.g. 12 random words {@link EncryptionUtils#getRandomWords(int,
|
* @param keyPhrase key used for encryption, e.g. 12 random words
|
||||||
* Context)}
|
* {@link EncryptionUtils#getRandomWords(int, Context)}
|
||||||
* @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
|
* @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
|
||||||
*/
|
*/
|
||||||
public static String encryptPrivateKey(String privateKey, String keyPhrase)
|
public static String encryptPrivateKey(String privateKey, String keyPhrase)
|
||||||
|
@ -619,8 +713,8 @@ public final class EncryptionUtils {
|
||||||
*/
|
*/
|
||||||
@SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
|
@SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD")
|
||||||
public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
|
public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
|
||||||
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
|
NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
|
||||||
IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
|
IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
String[] strings;
|
String[] strings;
|
||||||
|
|
||||||
|
@ -650,14 +744,14 @@ public final class EncryptionUtils {
|
||||||
String pemKey = decodeBase64BytesToString(decrypted);
|
String pemKey = decodeBase64BytesToString(decrypted);
|
||||||
|
|
||||||
return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
|
return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "")
|
||||||
.replace("-----END PRIVATE KEY-----", "");
|
.replace("-----END PRIVATE KEY-----", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String privateKeyToPEM(PrivateKey privateKey) {
|
public static String privateKeyToPEM(PrivateKey privateKey) {
|
||||||
String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
|
String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
|
||||||
|
|
||||||
return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
|
return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
|
||||||
+ "\n-----END PRIVATE KEY-----";
|
+ "\n-----END PRIVATE KEY-----";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -884,4 +978,10 @@ public final class EncryptionUtils {
|
||||||
|
|
||||||
return modulusPrivate.compareTo(modulusPublic) == 0;
|
return modulusPrivate.compareTo(modulusPublic) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean supportsSecureFiledrop(OCFile file, User user) {
|
||||||
|
return file.isEncrypted() &&
|
||||||
|
file.isFolder() &&
|
||||||
|
user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ public final class MimeTypeUtil {
|
||||||
|
|
||||||
if (WebdavEntry.MountType.GROUP == mountType || isGroupFolder) {
|
if (WebdavEntry.MountType.GROUP == mountType || isGroupFolder) {
|
||||||
drawableId = R.drawable.folder_group;
|
drawableId = R.drawable.folder_group;
|
||||||
} else if (isSharedViaLink) {
|
} else if (isSharedViaLink && !isEncrypted) {
|
||||||
drawableId = R.drawable.folder_shared_link;
|
drawableId = R.drawable.folder_shared_link;
|
||||||
} else if (isSharedViaUsers) {
|
} else if (isSharedViaUsers) {
|
||||||
drawableId = R.drawable.folder_shared_users;
|
drawableId = R.drawable.folder_shared_users;
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Nextcloud Android client application
|
||||||
|
|
||||||
|
@author Tobias Kaminsky
|
||||||
|
@author Andy Scherzinger
|
||||||
|
Copyright (C) 2020 Andy Scherzinger
|
||||||
|
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
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/add_public_share"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/sharee_list_item_size"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/share_icon_size"
|
||||||
|
android:layout_height="@dimen/share_icon_size"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="@dimen/standard_margin"
|
||||||
|
android:layout_marginEnd="@dimen/standard_margin"
|
||||||
|
android:background="@drawable/round_bgnd"
|
||||||
|
android:contentDescription="@string/share"
|
||||||
|
android:padding="@dimen/standard_half_padding"
|
||||||
|
android:src="@drawable/shared_via_link" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/share_permission_secure_file_drop"
|
||||||
|
android:textSize="@dimen/two_line_primary_text_size" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/add_new_secure_file_drop"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/add_new_secure_file_drop"
|
||||||
|
android:paddingStart="@dimen/standard_padding"
|
||||||
|
android:paddingEnd="@dimen/standard_padding"
|
||||||
|
android:src="@drawable/ic_plus" />
|
||||||
|
</LinearLayout>
|
|
@ -978,6 +978,7 @@
|
||||||
<string name="share_permission_view_only">View only</string>
|
<string name="share_permission_view_only">View only</string>
|
||||||
<string name="share_permission_can_edit">Can edit</string>
|
<string name="share_permission_can_edit">Can edit</string>
|
||||||
<string name="share_permission_file_drop">File drop</string>
|
<string name="share_permission_file_drop">File drop</string>
|
||||||
|
<string name="share_permission_secure_file_drop">Secure file drop</string>
|
||||||
<string name="share_permissions">Share Permissions</string>
|
<string name="share_permissions">Share Permissions</string>
|
||||||
<string name="advanced_settings">Advanced Settings</string>
|
<string name="advanced_settings">Advanced Settings</string>
|
||||||
<string name="common_next">Next</string>
|
<string name="common_next">Next</string>
|
||||||
|
@ -1065,6 +1066,7 @@
|
||||||
<string name="setup_e2e">During setup of end-to-end encryption, you will receive a random 12 word mnemonic, which you will need to open your files on other devices. This will only be stored on this device, and can be shown again in this screen. Please note it down in a secure place!</string>
|
<string name="setup_e2e">During setup of end-to-end encryption, you will receive a random 12 word mnemonic, which you will need to open your files on other devices. This will only be stored on this device, and can be shown again in this screen. Please note it down in a secure place!</string>
|
||||||
<string name="error_showing_encryption_dialog">Error showing setup encryption dialog!</string>
|
<string name="error_showing_encryption_dialog">Error showing setup encryption dialog!</string>
|
||||||
<string name="prefs_keys_exist">Add end-to-end encryption to this client</string>
|
<string name="prefs_keys_exist">Add end-to-end encryption to this client</string>
|
||||||
|
<string name="add_new_secure_file_drop">add new secure file drop</string>
|
||||||
<string name="scan_page">Scan page</string>
|
<string name="scan_page">Scan page</string>
|
||||||
<string name="done">Done</string>
|
<string name="done">Done</string>
|
||||||
<string name="document_scan_pdf_generation_in_progress">Generating PDF…</string>
|
<string name="document_scan_pdf_generation_in_progress">Generating PDF…</string>
|
||||||
|
|
|
@ -87,7 +87,8 @@ class ShareeListAdapterTest {
|
||||||
null,
|
null,
|
||||||
user.accountName,
|
user.accountName,
|
||||||
user,
|
user,
|
||||||
viewThemeUtils
|
viewThemeUtils,
|
||||||
|
false
|
||||||
)
|
)
|
||||||
sut.sortShares()
|
sut.sortShares()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue