diff --git a/app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java b/app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java index d346008e8b..41046243a9 100644 --- a/app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java +++ b/app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java @@ -26,8 +26,8 @@ import android.text.TextUtils; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; -import com.nextcloud.test.RetryTestRule; import com.nextcloud.test.RandomStringGenerator; +import com.nextcloud.test.RetryTestRule; import com.owncloud.android.datamodel.DecryptedFolderMetadata; import com.owncloud.android.datamodel.EncryptedFolderMetadata; 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.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Random; 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.assertTrue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; @RunWith(AndroidJUnit4.class) public class EncryptionTestIT { @@ -406,6 +408,58 @@ public class EncryptionTestIT { } } + @Test + public void filedrop() throws Exception { + DecryptedFolderMetadata decryptedFolderMetadata1 = generateFolderMetadata(); + + // add filedrop + Map 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() { + }); + + // 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) { // Add new file // Always generate new diff --git a/app/src/main/java/com/owncloud/android/datamodel/DecryptedFolderMetadata.java b/app/src/main/java/com/owncloud/android/datamodel/DecryptedFolderMetadata.java index 2fd3960c50..49792c450c 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/DecryptedFolderMetadata.java +++ b/app/src/main/java/com/owncloud/android/datamodel/DecryptedFolderMetadata.java @@ -24,6 +24,8 @@ package com.owncloud.android.datamodel; import java.util.HashMap; import java.util.Map; +import androidx.annotation.VisibleForTesting; + /** * Decrypted class representation of metadata json of folder metadata. */ @@ -31,6 +33,8 @@ public class DecryptedFolderMetadata { private Metadata metadata; private Map files; + private Map filedrop; + public DecryptedFolderMetadata() { this.metadata = new Metadata(); this.files = new HashMap<>(); @@ -57,6 +61,15 @@ public class DecryptedFolderMetadata { this.files = files; } + @VisibleForTesting + public void setFiledrop(Map filedrop) { + this.filedrop = filedrop; + } + + public Map getFiledrop() { + return filedrop; + } + public static class Metadata { private Map metadataKeys; // each keys is encrypted on its own, decrypt on use private Sharing sharing; diff --git a/app/src/main/java/com/owncloud/android/datamodel/EncryptedFolderMetadata.java b/app/src/main/java/com/owncloud/android/datamodel/EncryptedFolderMetadata.java index 12f18c6367..228d9b186e 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/EncryptedFolderMetadata.java +++ b/app/src/main/java/com/owncloud/android/datamodel/EncryptedFolderMetadata.java @@ -30,9 +30,14 @@ public class EncryptedFolderMetadata { private DecryptedFolderMetadata.Metadata metadata; private Map files; - public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata, Map files) { + private Map filedrop; + + public EncryptedFolderMetadata(DecryptedFolderMetadata.Metadata metadata, + Map files, + Map filesdrop) { this.metadata = metadata; this.files = files; + this.filedrop = filesdrop; } public DecryptedFolderMetadata.Metadata getMetadata() { @@ -40,7 +45,11 @@ public class EncryptedFolderMetadata { } public Map getFiles() { - return this.files; + return files; + } + + public Map getFiledrop() { + return filedrop; } public void setMetadata(DecryptedFolderMetadata.Metadata metadata) { @@ -58,19 +67,19 @@ public class EncryptedFolderMetadata { private int metadataKey; public String getEncrypted() { - return this.encrypted; + return encrypted; } public String getInitializationVector() { - return this.initializationVector; + return initializationVector; } public String getAuthenticationTag() { - return this.authenticationTag; + return authenticationTag; } public int getMetadataKey() { - return this.metadataKey; + return metadataKey; } public void setEncrypted(String encrypted) { diff --git a/app/src/main/java/com/owncloud/android/operations/CreateShareViaLinkOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateShareViaLinkOperation.java index 6281daf3c6..5954250897 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateShareViaLinkOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateShareViaLinkOperation.java @@ -40,6 +40,7 @@ public class CreateShareViaLinkOperation extends SyncOperation { private String path; private String password; + private int permissions = OCShare.NO_PERMISSION; public CreateShareViaLinkOperation(String path, String password, FileDataStorageManager storageManager) { super(storageManager); @@ -48,6 +49,11 @@ public class CreateShareViaLinkOperation extends SyncOperation { this.password = password; } + public CreateShareViaLinkOperation(String path, FileDataStorageManager storageManager, int permissions) { + this(path, null, storageManager); + this.permissions = permissions; + } + @Override protected RemoteOperationResult run(OwnCloudClient client) { CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path, @@ -55,7 +61,7 @@ public class CreateShareViaLinkOperation extends SyncOperation { "", false, password, - OCShare.NO_PERMISSION); + permissions); createOp.setGetShareDetails(true); RemoteOperationResult result = createOp.execute(client); diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 2c6eba756e..b6df79022b 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -420,13 +420,12 @@ public class RefreshFolderOperation extends RemoteOperation { return result; } - private void removeLocalFolder() { if (mStorageManager.fileExists(mLocalFolder.getFileId())) { String currentSavePath = FileStorageUtils.getSavePath(user.getAccountName()); mStorageManager.removeFolder( - mLocalFolder, - true, + mLocalFolder, + true, mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath) ); } diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java index 5f30194726..3656b8696f 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java @@ -156,13 +156,13 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation { // return success return result; } catch (NoSuchAlgorithmException | - IOException | - InvalidKeyException | - InvalidAlgorithmParameterException | - NoSuchPaddingException | - BadPaddingException | - IllegalBlockSizeException | - InvalidKeySpecException e) { + IOException | + InvalidKeyException | + InvalidAlgorithmParameterException | + NoSuchPaddingException | + BadPaddingException | + IllegalBlockSizeException | + InvalidKeySpecException e) { result = new RemoteOperationResult(e); Log_OC.e(TAG, "Remove " + remotePath + ": " + result.getLogMessage(), e); 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 35db51a579..ce6ca4697e 100644 --- a/app/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/app/src/main/java/com/owncloud/android/services/OperationsService.java @@ -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.resources.files.RestoreFileVersionRemoteOperation; 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.users.GetUserInfoRemoteOperation; 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 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_UNSHARE = "UNSHARE"; public static final String ACTION_UPDATE_PUBLIC_SHARE = "UPDATE_PUBLIC_SHARE"; @@ -519,6 +521,13 @@ public class OperationsService extends Service { } 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: shareId = operationIntent.getLongExtra(EXTRA_SHARE_ID, -1); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java index fd07bf988e..97796d4b68 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java @@ -24,6 +24,7 @@ import com.nextcloud.client.account.User; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment; import com.owncloud.android.ui.fragment.FileDetailSharingFragment; +import com.owncloud.android.utils.EncryptionUtils; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -34,8 +35,8 @@ import androidx.fragment.app.FragmentStatePagerAdapter; * File details pager adapter. */ public class FileDetailTabAdapter extends FragmentStatePagerAdapter { - private OCFile file; - private User user; + private final OCFile file; + private final User user; private FileDetailSharingFragment fileDetailSharingFragment; private FileDetailActivitiesFragment fileDetailActivitiesFragment; @@ -71,9 +72,15 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter { @Override public int getCount() { 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 { - return 2; // show activities and sharing tab + // unencrypted files/folders + return 2; } } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java index 4c03f82e3d..25d13ccd2e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.java @@ -76,22 +76,28 @@ class LinkShareViewHolder extends RecyclerView.ViewHolder { String text = String.format(context.getString(R.string.share_link_with_label), publicShare.getLabel()); binding.name.setText(text); } 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); } String permissionName = SharingMenuHelper.getPermissionName(context, publicShare); - setPermissionName(permissionName); + setPermissionName(publicShare, permissionName); binding.copyLink.setOnClickListener(v -> listener.copyLink(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) { - if (!TextUtils.isEmpty(permissionName)) { + private void setPermissionName(OCShare publicShare, String permissionName) { + if (!TextUtils.isEmpty(permissionName) && !SharingMenuHelper.isSecureFileDrop(publicShare)) { binding.permissionName.setText(permissionName); binding.permissionName.setVisibility(View.VISIBLE); viewThemeUtils.androidx.colorPrimaryTextViewElement(binding.permissionName); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NewSecureFileDropViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/NewSecureFileDropViewHolder.kt new file mode 100644 index 0000000000..117c2f5a45 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/adapter/NewSecureFileDropViewHolder.kt @@ -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 . + */ +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() } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 3cd53bdcd7..a5f44e1a95 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -45,6 +45,7 @@ import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.BitmapUtils import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.EncryptionUtils import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils @@ -237,7 +238,11 @@ class OCFileListDelegate( bindGridMetadataViews(file, gridViewHolder) // 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 if (shouldHideShare) { gridViewHolder.shared.visibility = View.GONE diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java index d6a8e303d0..615f28bad8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapter.java @@ -35,6 +35,7 @@ import com.owncloud.android.R; import com.owncloud.android.databinding.FileDetailsShareInternalShareLinkBinding; import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding; import com.owncloud.android.databinding.FileDetailsSharePublicLinkAddNewItemBinding; +import com.owncloud.android.databinding.FileDetailsShareSecureFileDropAddNewItemBinding; import com.owncloud.android.databinding.FileDetailsShareShareItemBinding; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; @@ -62,19 +63,22 @@ public class ShareeListAdapter extends RecyclerView.Adapter shares, ShareeListAdapterListener listener, String userId, User user, - final ViewThemeUtils viewThemeUtils) { + final ViewThemeUtils viewThemeUtils, + boolean encrypted) { this.fileActivity = fileActivity; this.shares = shares; this.listener = listener; this.userId = userId; this.user = user; this.viewThemeUtils = viewThemeUtils; + this.encrypted = encrypted; avatarRadiusDimension = fileActivity.getResources().getDimension(R.dimen.user_icon_radius); @@ -99,11 +103,19 @@ public class ShareeListAdapter extends RecyclerView.Adapter getShares() { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java index 4e3c514965..5fc70e8f4e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ShareeListAdapterListener.java @@ -36,6 +36,8 @@ public interface ShareeListAdapterListener { void createPublicShareLink(); + void createSecureFileDrop(); + void requestPasswordForShare(OCShare share, boolean askForPassword); void showPermissionsDialog(OCShare share); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index 67731fb931..30c40cba47 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -70,6 +70,7 @@ import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.EncryptionUtils; import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; @@ -272,12 +273,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener, binding.tabLayout.removeAllTabs(); 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)); } + viewThemeUtils.material.themeTabLayout(binding.tabLayout); + final FileDetailTabAdapter adapter = new FileDetailTabAdapter(getFragmentManager(), getFile(), user); binding.pager.setAdapter(adapter); binding.pager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.tabLayout) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java index 401a23a2b3..cde91289f7 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java @@ -163,7 +163,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda this, userId, user, - viewThemeUtils)); + viewThemeUtils, + file.isEncrypted())); binding.sharesList.setLayoutManager(new LinearLayoutManager(getContext())); setupView(); @@ -193,18 +194,22 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda private void setupView() { setShareWithYou(); - FileDetailSharingFragmentHelper.setupSearchView( - (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), - binding.searchView, - fileActivity.getComponentName()); - viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - - if (file.canReshare()) { - binding.searchView.setQueryHint(getResources().getString(R.string.share_search)); + if (file.isEncrypted()) { + binding.searchContainer.setVisibility(View.GONE); } else { - binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - disableSearchView(binding.searchView); + FileDetailSharingFragmentHelper.setupSearchView( + (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), + 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) { if (file.isSharedViaLink()) { if (TextUtils.isEmpty(publicShare.getShareLink())) { @@ -425,6 +435,19 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda 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())) { final OCShare ocShare = new OCShare(); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java index aea75ca074..f71c258553 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingMenuBottomSheetDialog.java @@ -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.ShareType; import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.fragment.util.SharingMenuHelper; import com.owncloud.android.utils.theme.ViewThemeUtils; /** @@ -41,7 +42,6 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog { private final FileDetailsSharingMenuBottomSheetActions actions; private final OCShare ocShare; private final ViewThemeUtils viewThemeUtils; - public FileDetailSharingMenuBottomSheetDialog(FileActivity fileActivity, FileDetailsSharingMenuBottomSheetActions actions, OCShare ocShare, @@ -88,6 +88,11 @@ public class FileDetailSharingMenuBottomSheetDialog extends BottomSheetDialog { binding.menuShareAddAnotherLink.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() { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java index 793971664d..6e1ca9bb8f 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/util/SharingMenuHelper.java @@ -122,11 +122,21 @@ public final class SharingMenuHelper { 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) { if (SharingMenuHelper.isUploadAndEditingAllowed(share)) { return context.getResources().getString(R.string.share_permission_can_edit); } else if (SharingMenuHelper.isReadOnly(share)) { 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)) { return context.getResources().getString(R.string.share_permission_file_drop); } 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 a061c6b17a..b9e05e1014 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 @@ -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) { List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), ShareType.PUBLIC_LINK, diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java index 7b3b535ac1..d215d3e542 100644 --- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java +++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java @@ -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.UnlockFileRemoteOperation; import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation; +import com.owncloud.android.lib.resources.status.NextcloudVersion; import com.owncloud.android.operations.UploadException; import org.apache.commons.httpclient.HttpStatus; @@ -147,18 +148,22 @@ public final class EncryptionUtils { * @return EncryptedFolderMetadata encrypted folder metadata */ public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata, - String privateKey) - throws NoSuchAlgorithmException, InvalidKeyException, - InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, - IllegalBlockSizeException, InvalidKeySpecException { + String privateKey + ) + throws NoSuchAlgorithmException, InvalidKeyException, + InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, + IllegalBlockSizeException, InvalidKeySpecException { HashMap files = new HashMap<>(); + HashMap filesdrop = new HashMap<>(); EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata - .getMetadata(), files); + .getMetadata(), + files, + filesdrop); // Encrypt each file in "files" for (Map.Entry entry : decryptedFolderMetadata - .getFiles().entrySet()) { + .getFiles().entrySet()) { String key = entry.getKey(); DecryptedFolderMetadata.DecryptedFile decryptedFile = entry.getValue(); @@ -168,8 +173,8 @@ public final class EncryptionUtils { encryptedFile.setAuthenticationTag(decryptedFile.getAuthenticationTag()); byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes(EncryptionUtils.decryptStringAsymmetric( - decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()), - privateKey)); + decryptedFolderMetadata.getMetadata().getMetadataKeys().get(encryptedFile.getMetadataKey()), + privateKey)); // encrypt String dataJson = EncryptionUtils.serializeJSON(decryptedFile.getEncrypted()); @@ -181,21 +186,42 @@ public final class EncryptionUtils { return encryptedFolderMetadata; } + @VisibleForTesting + public static void encryptFileDropFiles(DecryptedFolderMetadata decryptedFolderMetadata, EncryptedFolderMetadata encryptedFolderMetadata, String cert) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException { + final Map filesdrop = encryptedFolderMetadata.getFiledrop(); + for (Map.Entry 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 */ public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata, String privateKey) - throws NoSuchAlgorithmException, InvalidKeyException, - InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, - IllegalBlockSizeException, InvalidKeySpecException { + throws NoSuchAlgorithmException, InvalidKeyException, + InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, + IllegalBlockSizeException, InvalidKeySpecException { HashMap files = new HashMap<>(); DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata( - encryptedFolderMetadata.getMetadata(), files); + encryptedFolderMetadata.getMetadata(), files); for (Map.Entry entry : encryptedFolderMetadata - .getFiles().entrySet()) { + .getFiles().entrySet()) { String key = entry.getKey(); EncryptedFolderMetadata.EncryptedFile encryptedFile = entry.getValue(); @@ -205,18 +231,45 @@ public final class EncryptionUtils { decryptedFile.setAuthenticationTag(encryptedFile.getAuthenticationTag()); byte[] decryptedMetadataKey = EncryptionUtils.decodeStringToBase64Bytes( - EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata() - .getMetadataKeys().get(encryptedFile.getMetadataKey()), privateKey)); + EncryptionUtils.decryptStringAsymmetric(decryptedFolderMetadata.getMetadata() + .getMetadataKeys().get(encryptedFile.getMetadataKey()), + privateKey)); // decrypt String dataJson = EncryptionUtils.decryptStringSymmetric(encryptedFile.getEncrypted(), decryptedMetadataKey); decryptedFile.setEncrypted(EncryptionUtils.deserializeJSON(dataJson, - new TypeToken() { - })); + new TypeToken() { + })); files.put(key, decryptedFile); } + Map fileDrop = encryptedFolderMetadata.getFiledrop(); + + if (fileDrop != null) { + for (Map.Entry 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() { + })); + + files.put(key, decryptedFile); + + // remove from filedrop + fileDrop.remove(key); + } + } + return decryptedFolderMetadata; } @@ -226,8 +279,10 @@ public final class EncryptionUtils { * @return decrypted metadata or null */ public static @Nullable - DecryptedFolderMetadata downloadFolderMetadata(OCFile folder, OwnCloudClient client, - Context context, User user) { + DecryptedFolderMetadata downloadFolderMetadata(OCFile folder, + OwnCloudClient client, + Context context, + User user) { RemoteOperationResult getMetadataOperationResult = new GetMetadataRemoteOperation(folder.getLocalId()) .execute(client); @@ -241,11 +296,48 @@ public final class EncryptionUtils { String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY); EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.deserializeJSON( - serializedEncryptedMetadata, new TypeToken() { - }); + serializedEncryptedMetadata, new TypeToken() { + }); 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) { Log_OC.e(TAG, e.getMessage()); return null; @@ -287,13 +379,14 @@ public final class EncryptionUtils { /** * @param ocFile file do crypt * @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 */ public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv) - throws NoSuchAlgorithmException, - InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, - BadPaddingException, IllegalBlockSizeException, IOException { + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException, IOException { File file = new File(ocFile.getStoragePath()); return encryptFile(file, encryptionKeyBytes, iv); @@ -302,13 +395,14 @@ public final class EncryptionUtils { /** * @param file file do crypt * @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 */ public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv) - throws NoSuchAlgorithmException, - InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, - BadPaddingException, IllegalBlockSizeException, IOException { + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException, IOException { Cipher cipher = Cipher.getInstance(AES_CIPHER); @@ -323,7 +417,7 @@ public final class EncryptionUtils { byte[] cryptedBytes = cipher.doFinal(fileBytes); String authenticationTag = encodeBytesToBase64String(Arrays.copyOfRange(cryptedBytes, - cryptedBytes.length - (128 / 8), cryptedBytes.length)); + cryptedBytes.length - (128 / 8), cryptedBytes.length)); return new EncryptedFile(cryptedBytes, authenticationTag); } @@ -336,9 +430,9 @@ public final class EncryptionUtils { * @return decrypted byte[] */ public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag) - throws NoSuchAlgorithmException, - InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, - BadPaddingException, IllegalBlockSizeException, IOException { + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException, IOException { Cipher cipher = Cipher.getInstance(AES_CIPHER); @@ -352,7 +446,7 @@ public final class EncryptionUtils { // check authentication tag byte[] extractedAuthenticationTag = Arrays.copyOfRange(fileBytes, - fileBytes.length - (128 / 8), fileBytes.length); + fileBytes.length - (128 / 8), fileBytes.length); if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) { throw new SecurityException("Tag not correct"); @@ -372,16 +466,16 @@ public final class EncryptionUtils { } /** - * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding - * Asymmetric encryption, with private and public key + * Encrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private + * and public key * * @param string String to encrypt * @param cert contains public key in it * @return encrypted string */ public static String encryptStringAsymmetric(String string, String cert) - throws NoSuchAlgorithmException, - NoSuchPaddingException, InvalidKeyException, + throws NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, CertificateException { @@ -418,17 +512,17 @@ public final class EncryptionUtils { /** - * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding - * Asymmetric encryption, with private and public key + * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private + * and public key * * @param string string to decrypt * @param privateKeyString private key * @return decrypted string */ public static String decryptStringAsymmetric(String string, String privateKeyString) - throws NoSuchAlgorithmException, - NoSuchPaddingException, InvalidKeyException, - BadPaddingException, IllegalBlockSizeException, + throws NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { Cipher cipher = Cipher.getInstance(RSA_CIPHER); @@ -513,17 +607,17 @@ public final class EncryptionUtils { /** - * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding - * Asymmetric encryption, with private and public key + * Decrypt string with RSA algorithm, ECB mode, OAEPWithSHA-256AndMGF1 padding Asymmetric encryption, with private + * and public key * * @param string string to decrypt * @param encryptionKeyBytes key from metadata * @return decrypted string */ public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes) - throws NoSuchAlgorithmException, - InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, - BadPaddingException, IllegalBlockSizeException { + throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException { 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 * * @param privateKey byte64 encoded string representation of private key - * @param keyPhrase key used for encryption, e.g. 12 random words {@link EncryptionUtils#getRandomWords(int, - * Context)} + * @param keyPhrase key used for encryption, e.g. 12 random words + * {@link EncryptionUtils#getRandomWords(int, Context)} * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string */ public static String encryptPrivateKey(String privateKey, String keyPhrase) @@ -619,8 +713,8 @@ public final class EncryptionUtils { */ @SuppressFBWarnings("UCPM_USE_CHARACTER_PARAMETERIZED_METHOD") public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException, - NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, - IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException { + NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, + IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException { String[] strings; @@ -650,14 +744,14 @@ public final class EncryptionUtils { String pemKey = decodeBase64BytesToString(decrypted); return pemKey.replaceAll("\n", "").replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", ""); + .replace("-----END PRIVATE KEY-----", ""); } public static String privateKeyToPEM(PrivateKey privateKey) { String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded()); 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; } + + public static boolean supportsSecureFiledrop(OCFile file, User user) { + return file.isEncrypted() && + file.isFolder() && + user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_26); + } } diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index f60568204e..5245166053 100644 --- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -150,7 +150,7 @@ public final class MimeTypeUtil { if (WebdavEntry.MountType.GROUP == mountType || isGroupFolder) { drawableId = R.drawable.folder_group; - } else if (isSharedViaLink) { + } else if (isSharedViaLink && !isEncrypted) { drawableId = R.drawable.folder_shared_link; } else if (isSharedViaUsers) { drawableId = R.drawable.folder_shared_users; diff --git a/app/src/main/res/layout/file_details_share_secure_file_drop_add_new_item.xml b/app/src/main/res/layout/file_details_share_secure_file_drop_add_new_item.xml new file mode 100644 index 0000000000..2d856eb83a --- /dev/null +++ b/app/src/main/res/layout/file_details_share_secure_file_drop_add_new_item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 59d6013d1a..25d5e9cde7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -978,6 +978,7 @@ View only Can edit File drop + Secure file drop Share Permissions Advanced Settings Next @@ -1065,6 +1066,7 @@ 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! Error showing setup encryption dialog! Add end-to-end encryption to this client + add new secure file drop Scan page Done Generating PDF… diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt b/app/src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt index cef8d08d4f..f81c866a24 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/ShareeListAdapterTest.kt @@ -87,7 +87,8 @@ class ShareeListAdapterTest { null, user.accountName, user, - viewThemeUtils + viewThemeUtils, + false ) sut.sortShares()