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

268 lines
12 KiB
Java

/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020-2023 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2021 TSI-mc
* SPDX-FileCopyrightText: 2016 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2015 David A. Velasco <dvelasco@solidgear.es>
* SPDX-License-Identifier: GPL-2.0-only AND AGPL-3.0-or-later
*/
package com.owncloud.android.operations;
import android.content.Context;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.network.ClientFactoryImpl;
import com.nextcloud.common.NextcloudClient;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.FileUtils;
import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation;
import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.lib.resources.shares.ShareType;
import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation;
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.EncryptionUtilsV2;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Creates a new private share for a given file.
*/
public class CreateShareWithShareeOperation extends SyncOperation {
private final String path;
private final String shareeName;
private final ShareType shareType;
private final int permissions;
private final String noteMessage;
private final String sharePassword;
private final boolean hideFileDownload;
private final long expirationDateInMillis;
private String label;
private final Context context;
private final User user;
private ArbitraryDataProvider arbitraryDataProvider;
private static final Set<ShareType> supportedShareTypes = new HashSet<>(Arrays.asList(ShareType.USER,
ShareType.GROUP,
ShareType.FEDERATED,
ShareType.EMAIL,
ShareType.ROOM,
ShareType.CIRCLE));
/**
* Constructor.
*
* @param path Full path of the file/folder being shared.
* @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}
* are the only valid values for the moment.
* @param permissions Share permissions key as detailed in <a
* href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html">OCS
* Share API</a>.
*/
public CreateShareWithShareeOperation(String path,
String shareeName,
ShareType shareType,
int permissions,
String noteMessage,
String sharePassword,
long expirationDateInMillis,
boolean hideFileDownload,
FileDataStorageManager storageManager,
Context context,
User user,
ArbitraryDataProvider arbitraryDataProvider) {
super(storageManager);
if (!supportedShareTypes.contains(shareType)) {
throw new IllegalArgumentException("Illegal share type " + shareType);
}
this.path = path;
this.shareeName = shareeName;
this.shareType = shareType;
this.permissions = permissions;
this.expirationDateInMillis = expirationDateInMillis;
this.hideFileDownload = hideFileDownload;
this.noteMessage = noteMessage;
this.sharePassword = sharePassword;
this.context = context;
this.user = user;
this.arbitraryDataProvider = arbitraryDataProvider;
}
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
OCFile folder = getStorageManager().getFileByDecryptedRemotePath(path);
if (folder == null) {
throw new IllegalArgumentException("Trying to share on a null folder: " + path);
}
boolean isEncrypted = folder.isEncrypted();
String token = null;
long newCounter = folder.getE2eCounter() + 1;
// E2E: lock folder
if (isEncrypted) {
try {
String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
if (publicKey.equals("")) {
NextcloudClient nextcloudClient = new ClientFactoryImpl(context).createNextcloudClient(user);
RemoteOperationResult<String> result = new GetPublicKeyRemoteOperation(shareeName).execute(nextcloudClient);
if (result.isSuccess()) {
// store it
EncryptionUtils.savePublicKey(
user,
result.getResultData(),
shareeName,
arbitraryDataProvider
);
} else {
RemoteOperationResult e = new RemoteOperationResult(new IllegalStateException());
e.setMessage(context.getString(R.string.secure_share_not_set_up));
return e;
}
}
token = EncryptionUtils.lockFolder(folder, client, newCounter);
} catch (UploadException | ClientFactory.CreationException e) {
return new RemoteOperationResult(e);
}
}
CreateShareRemoteOperation operation = new CreateShareRemoteOperation(
path,
shareType,
shareeName,
false,
sharePassword,
permissions,
noteMessage
);
operation.setGetShareDetails(true);
RemoteOperationResult shareResult = operation.execute(client);
if (!shareResult.isSuccess() || shareResult.getData().size() == 0) {
// something went wrong
return shareResult;
}
// E2E: update metadata
if (isEncrypted) {
Object object = EncryptionUtils.downloadFolderMetadata(folder,
client,
context,
user
);
if (object instanceof DecryptedFolderMetadataFileV1) {
throw new RuntimeException("Trying to share on e2e v1!");
}
DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
boolean metadataExists;
if (metadata == null) {
String cert = EncryptionUtils.retrievePublicKeyForUser(user, context);
metadata = new EncryptionUtilsV2().createDecryptedFolderMetadataFile();
metadata.getUsers().add(new DecryptedUser(client.getUserId(), cert));
metadataExists = false;
} else {
metadataExists = true;
}
EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
// add sharee to metadata
String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
DecryptedFolderMetadataFile newMetadata = encryptionUtilsV2.addShareeToMetadata(metadata,
shareeName,
publicKey);
// upload metadata
metadata.getMetadata().setCounter(newCounter);
try {
encryptionUtilsV2.serializeAndUploadMetadata(folder,
newMetadata,
token,
client,
metadataExists,
context,
user,
getStorageManager());
} catch (UploadException e) {
return new RemoteOperationResult<>(new RuntimeException("Uploading metadata failed"));
}
// E2E: unlock folder
RemoteOperationResult<Void> unlockResult = EncryptionUtils.unlockFolder(folder, client, token);
if (!unlockResult.isSuccess()) {
return new RemoteOperationResult<>(new RuntimeException("Unlock failed"));
}
}
OCShare share = (OCShare) shareResult.getData().get(0);
// once creating share link update other information
UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
updateShareInfoOperation.setHideFileDownload(hideFileDownload);
updateShareInfoOperation.setNote(noteMessage);
updateShareInfoOperation.setLabel(label);
//update permissions for external share (will otherwise default to read-only)
updateShareInfoOperation.setPermissions(permissions);
// execute and save the result in database
RemoteOperationResult updateShareInfoResult = updateShareInfoOperation.execute(client);
if (updateShareInfoResult.isSuccess() && updateShareInfoResult.getData().size() > 0) {
OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
updateData(shareUpdated);
}
return shareResult;
}
private void updateData(OCShare share) {
// Update DB with the response
share.setPath(path);
share.setFolder(path.endsWith(FileUtils.PATH_SEPARATOR));
share.setPasswordProtected(!TextUtils.isEmpty(sharePassword));
getStorageManager().saveShare(share);
// Update OCFile with data from share: ShareByLink and publicLink
OCFile file = getStorageManager().getFileByPath(path);
if (file != null) {
file.setSharedWithSharee(true); // TODO - this should be done by the FileContentProvider, as part of getStorageManager().saveShare(share)
getStorageManager().saveFile(file);
}
}
public String getPath() {
return this.path;
}
public void setLabel(String label) {
this.label = label;
}
}