diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java index 78c6f415cf45..33d62cdcea0b 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java @@ -342,6 +342,11 @@ public void createFolder() { } + @Override + public void createEncryptedFolder() { + + } + @Override public void uploadFromApp() { diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index ecb979542eef..6b823bb64863 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -236,7 +236,7 @@ private void filterUnlock(List toHide, boolean fileLockingEnabled) { private void filterEncrypt(List toHide, boolean endToEndEncryptionEnabled) { if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || isGroupFolder() - || !endToEndEncryptionEnabled || !isEmptyFolder() || isShared()) { + || !endToEndEncryptionEnabled || !isEmptyFolder() || isShared() || isInSubFolder()) { toHide.add(R.id.action_encrypted); } } @@ -574,4 +574,15 @@ private boolean isShared() { } return false; } + + private boolean isInSubFolder() { + OCFile folder = files.iterator().next(); + OCFile parent = storageManager.getFileById(folder.getParentId()); + + if (parent == null) { + return false; + } + + return !OCFile.ROOT_PATH.equals(parent.getRemotePath()); + } } diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index f006888799f0..bd73f4ba0eed 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -60,16 +60,31 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper private RemoteFile createdRemoteFolder; private User user; private Context context; + private boolean encrypted; /** * Constructor */ - public CreateFolderOperation(String remotePath, User user, Context context, FileDataStorageManager storageManager) { + public CreateFolderOperation(String remotePath, + User user, + Context context, + FileDataStorageManager storageManager + ) { + this(remotePath, false, user, context, storageManager); + } + + public CreateFolderOperation(String remotePath, + boolean encrypted, + User user, + Context context, + FileDataStorageManager storageManager + ) { super(storageManager); this.remotePath = remotePath; this.user = user; this.context = context; + this.encrypted = encrypted; } @Override @@ -105,7 +120,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } return new RemoteOperationResult(new IllegalStateException("E2E not supported")); } else { - return normalCreate(client); + return normalCreate(client, encrypted); } } @@ -473,7 +488,7 @@ private String createRandomFileName(DecryptedFolderMetadataFileV1 metadata) { return encryptedFileName; } - private RemoteOperationResult normalCreate(OwnCloudClient client) { + private RemoteOperationResult normalCreate(OwnCloudClient client, boolean encrypted) { RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client); if (result.isSuccess()) { @@ -482,6 +497,21 @@ private RemoteOperationResult normalCreate(OwnCloudClient client) { createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0); saveFolderInDB(); + + if (encrypted) { + final OCFile folder = getStorageManager().getFileByDecryptedRemotePath(remotePath); + + final RemoteOperationResult remoteOperationResult = + new ToggleEncryptionRemoteOperation(folder.getLocalId(), + remotePath, + true) + .execute(client); + + if (remoteOperationResult.isSuccess()) { + folder.setEncrypted(true); + getStorageManager().saveFile(folder); + } + } } else { Log_OC.e(TAG, remotePath + " hasn't been created"); } diff --git a/app/src/main/java/com/owncloud/android/services/OperationsService.java b/app/src/main/java/com/owncloud/android/services/OperationsService.java index 372a3c88e089..efbd94c6666f 100644 --- a/app/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java @@ -84,6 +84,7 @@ public class OperationsService extends Service { public static final String EXTRA_ACCOUNT = "ACCOUNT"; public static final String EXTRA_SERVER_URL = "SERVER_URL"; public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; + public static final String EXTRA_ENCRYPTED = "ENCRYPTED"; public static final String EXTRA_NEWNAME = "NEWNAME"; public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY"; public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS"; @@ -685,7 +686,9 @@ private Pair newOperation(Intent operationIntent) { case ACTION_CREATE_FOLDER: remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); + boolean encrypted = operationIntent.getBooleanExtra(EXTRA_ENCRYPTED, false); operation = new CreateFolderOperation(remotePath, + encrypted, user, getApplicationContext(), fileDataStorageManager); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 3490e87b3f43..76f4bcf2be1c 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -323,6 +323,10 @@ public int getItemCount() { @Nullable public OCFile getItem(int position) { + if (position == -1) { + return null; + } + int newPosition = position; if (shouldShowHeader() && position > 0) { diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt index 463ea2c40aef..37d1345bdd56 100644 --- a/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt @@ -54,8 +54,9 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList @JvmField @Inject var keyboardUtils: KeyboardUtils? = null - private var mParentFolder: OCFile? = null + private var parentFolder: OCFile? = null private var positiveButton: MaterialButton? = null + private var encrypted = false private lateinit var binding: EditBoxDialogBinding @@ -84,7 +85,8 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList @Suppress("EmptyFunctionBlock") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - mParentFolder = arguments?.getParcelableArgument(ARG_PARENT_FOLDER, OCFile::class.java) + parentFolder = arguments?.getParcelableArgument(ARG_PARENT_FOLDER, OCFile::class.java) + encrypted = arguments?.getBoolean(ARG_ENCRYPTED) ?: false // Inflate the layout for the dialog val inflater = requireActivity().layoutInflater @@ -168,15 +170,20 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList DisplayUtils.showSnackMessage(requireActivity(), R.string.filename_forbidden_charaters_from_server) return } - val path = mParentFolder!!.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR - (requireActivity() as ComponentsGetter).fileOperationsHelper.createFolder(path) + val path = parentFolder!!.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR + (requireActivity() as ComponentsGetter).fileOperationsHelper.createFolder(path, encrypted) } } companion object { private const val ARG_PARENT_FOLDER = "PARENT_FOLDER" + private const val ARG_ENCRYPTED = "ENCRYPTED" const val CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT" + @JvmStatic + fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment { + return newInstance(parentFolder, false) + } /** * Public factory method to create new CreateFolderDialogFragment instances. * @@ -184,10 +191,11 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList * @return Dialog ready to show. */ @JvmStatic - fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment { + fun newInstance(parentFolder: OCFile?, encrypted: Boolean): CreateFolderDialogFragment { val frag = CreateFolderDialogFragment() val args = Bundle() args.putParcelable(ARG_PARENT_FOLDER, parentFolder) + args.putBoolean(ARG_ENCRYPTED, encrypted) frag.arguments = args return frag } diff --git a/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt index 495d230518e9..f49b8e169fbb 100644 --- a/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt +++ b/app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt @@ -10,8 +10,12 @@ package com.owncloud.android.ui.events * Event for set folder as encrypted/decrypted */ class EncryptionEvent( + @JvmField val localId: Long, + @JvmField val remoteId: String, + @JvmField val remotePath: String, + @JvmField val shouldBeEncrypted: Boolean ) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java index c28f1e9837f9..4b4bc13a9940 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java @@ -18,6 +18,11 @@ public interface OCFileListBottomSheetActions { */ void createFolder(); + /** + * creates an encrypted folder within the actual folder + */ + void createEncryptedFolder(); + /** * offers a file upload with the Android OS file picker to the current folder. */ diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java index 2deb69e0537d..0ff9447f3bf7 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java @@ -86,9 +86,8 @@ protected void onCreate(Bundle savedInstanceState) { binding.addToCloud.setText(getContext().getResources().getString(R.string.add_to_cloud, themeUtils.getDefaultDisplayNameForRootFolder(getContext()))); - OCCapability capability = fileActivity.getCapabilities(); - if (capability != null && - capability.getRichDocuments().isTrue() && + OCCapability capability = fileActivity.getStorageManager().getCapability(user.getAccountName()); + if (capability.getRichDocuments().isTrue() && capability.getRichDocumentsDirectEditing().isTrue() && capability.getRichDocumentsTemplatesAvailable().isTrue() && !file.isEncrypted()) { @@ -136,6 +135,12 @@ protected void onCreate(Bundle savedInstanceState) { binding.menuDirectCameraUpload.setVisibility(View.GONE); } + if (capability.getEndToEndEncryption().isTrue() && OCFile.ROOT_PATH.equals(file.getRemotePath())) { + binding.menuEncryptedMkdir.setVisibility(View.VISIBLE); + } else { + binding.menuEncryptedMkdir.setVisibility(View.GONE); + } + // create rich workspace if (editorUtils.isEditorAvailable(user, MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN) && @@ -170,6 +175,11 @@ private void setupClickListener() { dismiss(); }); + binding.menuEncryptedMkdir.setOnClickListener(v -> { + actions.createEncryptedFolder(); + dismiss(); + }); + binding.menuUploadFromApp.setOnClickListener(v -> { actions.uploadFromApp(); dismiss(); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 323050af2b87..8412496e95c1 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -509,6 +509,14 @@ public void createFolder() { .show(getActivity().getSupportFragmentManager(), DIALOG_CREATE_FOLDER); } + @Override + public void createEncryptedFolder() { + if (checkEncryptionIsSetup(null)) { + CreateFolderDialogFragment.newInstance(mFile, true) + .show(getActivity().getSupportFragmentManager(), DIALOG_CREATE_FOLDER); + } + } + @Override public void uploadFromApp() { Intent action = new Intent(Intent.ACTION_GET_CONTENT); @@ -1135,10 +1143,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { int position = data.getIntExtra(SetupEncryptionDialogFragment.ARG_POSITION, -1); OCFile file = mAdapter.getItem(position); - if (file != null) { - mContainerActivity.getFileOperationsHelper().toggleEncryption(file, true); - mAdapter.setEncryptionAttributeForItemID(file.getRemoteId(), true); + if (file == null) { + return; } + mContainerActivity.getFileOperationsHelper().toggleEncryption(file, true); + mAdapter.setEncryptionAttributeForItemID(file.getRemoteId(), true); // update state and view of this fragment searchFragment = false; @@ -1718,49 +1727,52 @@ protected RemoteOperation getSearchRemoteOperation(final User currentUser, final @Subscribe(threadMode = ThreadMode.BACKGROUND) public void onMessageEvent(EncryptionEvent event) { + if (checkEncryptionIsSetup(event.remoteId)) { + encryptFolder(event.localId, event.remoteId, event.remotePath, event.shouldBeEncrypted); + } + } + + private boolean checkEncryptionIsSetup(@Nullable String remoteId) { final User user = accountManager.getUser(); // check if keys are stored String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY); String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY); - FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); - OCFile file = storageManager.getFileByRemoteId(event.getRemoteId()); - if (publicKey.isEmpty() || privateKey.isEmpty()) { Log_OC.d(TAG, "no public key for " + user.getAccountName()); + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); int position = -1; - if (file != null) { - position = mAdapter.getItemPosition(file); + if (remoteId != null) { + OCFile file = storageManager.getFileByRemoteId(remoteId); + if (file != null) { + position = mAdapter.getItemPosition(file); + } } SetupEncryptionDialogFragment dialog = SetupEncryptionDialogFragment.newInstance(user, position); dialog.setTargetFragment(this, SETUP_ENCRYPTION_REQUEST_CODE); dialog.show(getParentFragmentManager(), SETUP_ENCRYPTION_DIALOG_TAG); + + return false; } else { - // TODO E2E: if encryption fails, to not set it as encrypted! - encryptFolder(file, - event.getLocalId(), - event.getRemoteId(), - event.getRemotePath(), - event.getShouldBeEncrypted(), - publicKey, - privateKey, - storageManager); + return true; } } - private void encryptFolder(OCFile folder, - long localId, + private void encryptFolder(long localId, String remoteId, String remotePath, - boolean shouldBeEncrypted, - String publicKeyString, - String privateKeyString, - FileDataStorageManager storageManager) { + boolean shouldBeEncrypted) { try { - Log_OC.d(TAG, "encrypt folder " + folder.getRemoteId()); + Log_OC.d(TAG, "encrypt folder " + remoteId); User user = accountManager.getUser(); + String publicKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PUBLIC_KEY); + String privateKey = arbitraryDataProvider.getValue(user, EncryptionUtils.PRIVATE_KEY); + + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); + OCFile folder = storageManager.getFileByRemoteId(remoteId); + OwnCloudClient client = clientFactory.create(user); RemoteOperationResult remoteOperationResult = new ToggleEncryptionRemoteOperation(localId, remotePath, @@ -1777,8 +1789,8 @@ private void encryptFolder(OCFile folder, // Update metadata Pair metadataPair = EncryptionUtils.retrieveMetadata(folder, client, - privateKeyString, - publicKeyString, + privateKey, + publicKey, storageManager, user, requireContext(), diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 25a5e4a739f4..329d24ab016b 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -948,11 +948,16 @@ public void removeFiles(Collection files, boolean onlyLocalCopy, boolean public void createFolder(String remotePath) { + createFolder(remotePath, false); + } + + public void createFolder(String remotePath, boolean encrypted) { // 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_ENCRYPTED, encrypted); mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); fileActivity.showLoadingDialog(fileActivity.getString(R.string.wait_a_moment));