Skip to content

Commit 99ae6f5

Browse files
committed
Allow to create encrypted folder directly from bottom sheet dialog from NC PR: nextcloud#10782
1 parent 9b69807 commit 99ae6f5

File tree

11 files changed

+229
-37
lines changed

11 files changed

+229
-37
lines changed

app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,11 @@ public void createFolder() {
344344

345345
}
346346

347+
@Override
348+
public void createEncryptedFolder() {
349+
350+
}
351+
347352
@Override
348353
public void uploadFromApp() {
349354

app/src/main/java/com/owncloud/android/files/FileMenuFilter.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ private void filterUnlock(List<Integer> toHide, boolean fileLockingEnabled) {
237237

238238
private void filterEncrypt(List<Integer> toHide, boolean endToEndEncryptionEnabled) {
239239
if (files.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder() || isGroupFolder()
240-
|| !endToEndEncryptionEnabled || !isEmptyFolder() || isShared()) {
240+
|| !endToEndEncryptionEnabled || !isEmptyFolder() || isShared() || isInSubFolder()) {
241241
toHide.add(R.id.action_encrypted);
242242
}
243243
}
@@ -581,4 +581,15 @@ private boolean isShared() {
581581
}
582582
return false;
583583
}
584+
585+
private boolean isInSubFolder() {
586+
OCFile folder = files.iterator().next();
587+
OCFile parent = storageManager.getFileById(folder.getParentId());
588+
589+
if (parent == null) {
590+
return false;
591+
}
592+
593+
return !OCFile.ROOT_PATH.equals(parent.getRemotePath());
594+
}
584595
}

app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,31 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
6060
private RemoteFile createdRemoteFolder;
6161
private User user;
6262
private Context context;
63+
private boolean encrypted;
6364

6465
/**
6566
* Constructor
6667
*/
67-
public CreateFolderOperation(String remotePath, User user, Context context, FileDataStorageManager storageManager) {
68+
public CreateFolderOperation(String remotePath,
69+
User user,
70+
Context context,
71+
FileDataStorageManager storageManager
72+
) {
73+
this(remotePath, false, user, context, storageManager);
74+
}
75+
76+
public CreateFolderOperation(String remotePath,
77+
boolean encrypted,
78+
User user,
79+
Context context,
80+
FileDataStorageManager storageManager
81+
) {
6882
super(storageManager);
6983

7084
this.remotePath = remotePath;
7185
this.user = user;
7286
this.context = context;
87+
this.encrypted = encrypted;
7388
}
7489

7590
@Override
@@ -105,7 +120,7 @@ protected RemoteOperationResult run(OwnCloudClient client) {
105120
}
106121
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
107122
} else {
108-
return normalCreate(client);
123+
return normalCreate(client, encrypted);
109124
}
110125
}
111126

@@ -473,7 +488,7 @@ private String createRandomFileName(DecryptedFolderMetadataFileV1 metadata) {
473488
return encryptedFileName;
474489
}
475490

476-
private RemoteOperationResult normalCreate(OwnCloudClient client) {
491+
private RemoteOperationResult normalCreate(OwnCloudClient client, boolean encrypted) {
477492
RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
478493

479494
if (result.isSuccess()) {
@@ -482,8 +497,118 @@ private RemoteOperationResult normalCreate(OwnCloudClient client) {
482497

483498
createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
484499
saveFolderInDB();
485-
} else {
486-
Log_OC.e(TAG, remotePath + " hasn't been created");
500+
501+
if (encrypted) {
502+
final OCFile folder = getStorageManager().getFileByDecryptedRemotePath(remotePath);
503+
504+
final RemoteOperationResult remoteOperationResult =
505+
new ToggleEncryptionRemoteOperation(folder.getLocalId(),
506+
remotePath,
507+
true)
508+
.execute(client);
509+
510+
if (remoteOperationResult.isSuccess()) {
511+
512+
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
513+
String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
514+
String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
515+
String token = null;
516+
E2EVersion e2EVersion = getStorageManager().getCapability(user.getAccountName()).getEndToEndEncryptionApiVersion();
517+
518+
try {
519+
// lock folder
520+
token = EncryptionUtils.lockFolder(folder, client);
521+
522+
if (e2EVersion == E2EVersion.V2_0) {
523+
// Update metadata
524+
Pair<Boolean, DecryptedFolderMetadataFile> metadataPair = EncryptionUtils.retrieveMetadata(folder,
525+
client,
526+
privateKey,
527+
publicKey,
528+
getStorageManager(),
529+
user,
530+
context,
531+
arbitraryDataProvider);
532+
533+
boolean metadataExists = metadataPair.first;
534+
DecryptedFolderMetadataFile metadata = metadataPair.second;
535+
536+
new EncryptionUtilsV2().serializeAndUploadMetadata(folder,
537+
metadata,
538+
token,
539+
client,
540+
metadataExists,
541+
context,
542+
user,
543+
getStorageManager());
544+
545+
// unlock folder
546+
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
547+
548+
if (unlockFolderResult.isSuccess()) {
549+
token = null;
550+
} else {
551+
// TODO E2E: do better
552+
throw new RuntimeException("Could not unlock folder!");
553+
}
554+
555+
} else if (e2EVersion == E2EVersion.V1_0 ||
556+
e2EVersion == E2EVersion.V1_1 ||
557+
e2EVersion == E2EVersion.V1_2
558+
) {
559+
// unlock folder
560+
RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolderV1(folder, client, token);
561+
562+
if (unlockFolderResult.isSuccess()) {
563+
token = null;
564+
} else {
565+
// TODO E2E: do better
566+
throw new RuntimeException("Could not unlock folder!");
567+
}
568+
} else if (e2EVersion == E2EVersion.UNKNOWN) {
569+
return new RemoteOperationResult(new IllegalStateException("E2E not supported"));
570+
}
571+
572+
folder.setEncrypted(true);
573+
getStorageManager().saveFile(folder);
574+
} catch (Throwable e) {
575+
if (token != null) {
576+
if (e2EVersion == E2EVersion.V2_0) {
577+
if (!EncryptionUtils.unlockFolder(folder, client, token).isSuccess()) {
578+
throw new RuntimeException("Could not clean up after failing folder creation!", e);
579+
}
580+
} else if (e2EVersion == E2EVersion.V1_0 ||
581+
e2EVersion == E2EVersion.V1_1 ||
582+
e2EVersion == E2EVersion.V1_2) {
583+
if (!EncryptionUtils.unlockFolderV1(folder, client, token).isSuccess()) {
584+
throw new RuntimeException("Could not clean up after failing folder creation!", e);
585+
}
586+
}
587+
}
588+
// TODO E2E: do better
589+
return new RemoteOperationResult(new Exception(e));
590+
} finally {
591+
// unlock folder
592+
if (token != null) {
593+
RemoteOperationResult unlockFolderResult = null;
594+
if (e2EVersion == E2EVersion.V2_0) {
595+
unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
596+
} else if (e2EVersion == E2EVersion.V1_0 ||
597+
e2EVersion == E2EVersion.V1_1 ||
598+
e2EVersion == E2EVersion.V1_2) {
599+
unlockFolderResult = EncryptionUtils.unlockFolderV1(folder, client, token);
600+
}
601+
602+
if (unlockFolderResult != null && !unlockFolderResult.isSuccess()) {
603+
// TODO E2E: do better
604+
throw new RuntimeException("Could not unlock folder!");
605+
}
606+
}
607+
}
608+
}
609+
} else {
610+
Log_OC.e(TAG, remotePath + " hasn't been created");
611+
}
487612
}
488613

489614
return result;

app/src/main/java/com/owncloud/android/services/OperationsService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public class OperationsService extends Service {
8484
public static final String EXTRA_ACCOUNT = "ACCOUNT";
8585
public static final String EXTRA_SERVER_URL = "SERVER_URL";
8686
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
87+
public static final String EXTRA_ENCRYPTED = "ENCRYPTED";
8788
public static final String EXTRA_NEWNAME = "NEWNAME";
8889
public static final String EXTRA_REMOVE_ONLY_LOCAL = "REMOVE_LOCAL_COPY";
8990
public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
@@ -685,7 +686,9 @@ private Pair<Target, RemoteOperation> newOperation(Intent operationIntent) {
685686

686687
case ACTION_CREATE_FOLDER:
687688
remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
689+
boolean encrypted = operationIntent.getBooleanExtra(EXTRA_ENCRYPTED, false);
688690
operation = new CreateFolderOperation(remotePath,
691+
encrypted,
689692
user,
690693
getApplicationContext(),
691694
fileDataStorageManager);

app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ public int getItemCount() {
337337

338338
@Nullable
339339
public OCFile getItem(int position) {
340+
if (position == -1) {
341+
return null;
342+
}
343+
340344
int newPosition = position;
341345

342346
if (shouldShowHeader() && position > 0) {

app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
6666

6767
private var parentFolder: OCFile? = null
6868
private var positiveButton: MaterialButton? = null
69+
private var encrypted = false
6970

7071
private lateinit var binding: EditBoxDialogBinding
7172

@@ -100,6 +101,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
100101
@Suppress("EmptyFunctionBlock")
101102
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
102103
parentFolder = arguments?.getParcelableArgument(ARG_PARENT_FOLDER, OCFile::class.java)
104+
encrypted = arguments?.getBoolean(ARG_ENCRYPTED) ?: false
103105

104106
val inflater = requireActivity().layoutInflater
105107
binding = EditBoxDialogBinding.inflate(inflater, null, false)
@@ -183,7 +185,7 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
183185
val path = parentFolder?.decryptedRemotePath + newFolderName + OCFile.PATH_SEPARATOR
184186
connectivityService.isNetworkAndServerAvailable { result ->
185187
if (result) {
186-
typedActivity<ComponentsGetter>()?.fileOperationsHelper?.createFolder(path)
188+
typedActivity<ComponentsGetter>()?.fileOperationsHelper?.createFolder(path, encrypted)
187189
} else {
188190
Log_OC.d(TAG, "Network not available, creating offline operation")
189191
fileDataStorageManager.addCreateFolderOfflineOperation(
@@ -201,18 +203,24 @@ class CreateFolderDialogFragment : DialogFragment(), DialogInterface.OnClickList
201203
companion object {
202204
private const val TAG = "CreateFolderDialogFragment"
203205
private const val ARG_PARENT_FOLDER = "PARENT_FOLDER"
206+
private const val ARG_ENCRYPTED = "ENCRYPTED"
204207
const val CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT"
205208

209+
@JvmStatic
210+
fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment {
211+
return newInstance(parentFolder, false)
212+
}
206213
/**
207214
* Public factory method to create new CreateFolderDialogFragment instances.
208215
*
209216
* @param parentFolder Folder to create
210217
* @return Dialog ready to show.
211218
*/
212219
@JvmStatic
213-
fun newInstance(parentFolder: OCFile?): CreateFolderDialogFragment {
220+
fun newInstance(parentFolder: OCFile?, encrypted: Boolean): CreateFolderDialogFragment {
214221
val bundle = Bundle().apply {
215222
putParcelable(ARG_PARENT_FOLDER, parentFolder)
223+
putBoolean(ARG_ENCRYPTED, encrypted)
216224
}
217225

218226
return CreateFolderDialogFragment().apply {

app/src/main/java/com/owncloud/android/ui/events/EncryptionEvent.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ package com.owncloud.android.ui.events
1010
* Event for set folder as encrypted/decrypted
1111
*/
1212
class EncryptionEvent(
13+
@JvmField
1314
val localId: Long,
15+
@JvmField
1416
val remoteId: String,
17+
@JvmField
1518
val remotePath: String,
19+
@JvmField
1620
val shouldBeEncrypted: Boolean
1721
)

app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public interface OCFileListBottomSheetActions {
1818
*/
1919
void createFolder();
2020

21+
/**
22+
* creates an encrypted folder within the actual folder
23+
*/
24+
void createEncryptedFolder();
25+
2126
/**
2227
* offers a file upload with the Android OS file picker to the current folder.
2328
*/

app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ protected void onCreate(Bundle savedInstanceState) {
8686
binding.addToCloud.setText(getContext().getResources().getString(R.string.add_to_cloud,
8787
themeUtils.getDefaultDisplayNameForRootFolder(getContext())));
8888

89-
OCCapability capability = fileActivity.getCapabilities();
90-
if (capability != null &&
91-
capability.getRichDocuments().isTrue() &&
89+
OCCapability capability = fileActivity.getStorageManager().getCapability(user.getAccountName());
90+
if (capability.getRichDocuments().isTrue() &&
9291
capability.getRichDocumentsDirectEditing().isTrue() &&
9392
capability.getRichDocumentsTemplatesAvailable().isTrue() &&
9493
!file.isEncrypted()) {
@@ -136,6 +135,12 @@ protected void onCreate(Bundle savedInstanceState) {
136135
binding.menuDirectCameraUpload.setVisibility(View.GONE);
137136
}
138137

138+
if (capability.getEndToEndEncryption().isTrue() && OCFile.ROOT_PATH.equals(file.getRemotePath())) {
139+
binding.menuEncryptedMkdir.setVisibility(View.VISIBLE);
140+
} else {
141+
binding.menuEncryptedMkdir.setVisibility(View.GONE);
142+
}
143+
139144
// create rich workspace
140145
if (editorUtils.isEditorAvailable(user,
141146
MimeTypeUtil.MIMETYPE_TEXT_MARKDOWN) &&
@@ -171,6 +176,11 @@ private void setupClickListener() {
171176
dismiss();
172177
});
173178

179+
binding.menuEncryptedMkdir.setOnClickListener(v -> {
180+
actions.createEncryptedFolder();
181+
dismiss();
182+
});
183+
174184
binding.menuUploadFromApp.setOnClickListener(v -> {
175185
actions.uploadFromApp();
176186
dismiss();

0 commit comments

Comments
 (0)