CreateShareWithShareeOperation.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author masensio
  5. * @author David A. Velasco
  6. * @author TSI-mc
  7. * Copyright (C) 2015 ownCloud Inc.
  8. * Copyright (C) 2023 TSI-mc
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. package com.owncloud.android.operations;
  24. import android.content.Context;
  25. import android.text.TextUtils;
  26. import com.nextcloud.client.account.User;
  27. import com.nextcloud.client.network.ClientFactory;
  28. import com.nextcloud.client.network.ClientFactoryImpl;
  29. import com.nextcloud.common.NextcloudClient;
  30. import com.owncloud.android.R;
  31. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  32. import com.owncloud.android.datamodel.FileDataStorageManager;
  33. import com.owncloud.android.datamodel.OCFile;
  34. import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
  35. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
  36. import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedUser;
  37. import com.owncloud.android.lib.common.OwnCloudClient;
  38. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  39. import com.owncloud.android.lib.resources.files.FileUtils;
  40. import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation;
  41. import com.owncloud.android.lib.resources.shares.OCShare;
  42. import com.owncloud.android.lib.resources.shares.ShareType;
  43. import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation;
  44. import com.owncloud.android.operations.common.SyncOperation;
  45. import com.owncloud.android.utils.EncryptionUtils;
  46. import com.owncloud.android.utils.EncryptionUtilsV2;
  47. import java.util.Arrays;
  48. import java.util.HashSet;
  49. import java.util.Set;
  50. /**
  51. * Creates a new private share for a given file.
  52. */
  53. public class CreateShareWithShareeOperation extends SyncOperation {
  54. private final String path;
  55. private final String shareeName;
  56. private final ShareType shareType;
  57. private final int permissions;
  58. private final String noteMessage;
  59. private final String sharePassword;
  60. private final boolean hideFileDownload;
  61. private final long expirationDateInMillis;
  62. private String label;
  63. private final Context context;
  64. private final User user;
  65. private ArbitraryDataProvider arbitraryDataProvider;
  66. private static final Set<ShareType> supportedShareTypes = new HashSet<>(Arrays.asList(ShareType.USER,
  67. ShareType.GROUP,
  68. ShareType.FEDERATED,
  69. ShareType.EMAIL,
  70. ShareType.ROOM,
  71. ShareType.CIRCLE));
  72. /**
  73. * Constructor.
  74. *
  75. * @param path Full path of the file/folder being shared.
  76. * @param shareeName User or group name of the target sharee.
  77. * @param shareType Type of share determines type of sharee; {@link ShareType#USER} and {@link ShareType#GROUP}
  78. * are the only valid values for the moment.
  79. * @param permissions Share permissions key as detailed in <a
  80. * href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html">OCS
  81. * Share API</a>.
  82. */
  83. public CreateShareWithShareeOperation(String path,
  84. String shareeName,
  85. ShareType shareType,
  86. int permissions,
  87. String noteMessage,
  88. String sharePassword,
  89. long expirationDateInMillis,
  90. boolean hideFileDownload,
  91. FileDataStorageManager storageManager,
  92. Context context,
  93. User user,
  94. ArbitraryDataProvider arbitraryDataProvider) {
  95. super(storageManager);
  96. if (!supportedShareTypes.contains(shareType)) {
  97. throw new IllegalArgumentException("Illegal share type " + shareType);
  98. }
  99. this.path = path;
  100. this.shareeName = shareeName;
  101. this.shareType = shareType;
  102. this.permissions = permissions;
  103. this.expirationDateInMillis = expirationDateInMillis;
  104. this.hideFileDownload = hideFileDownload;
  105. this.noteMessage = noteMessage;
  106. this.sharePassword = sharePassword;
  107. this.context = context;
  108. this.user = user;
  109. this.arbitraryDataProvider = arbitraryDataProvider;
  110. }
  111. @Override
  112. protected RemoteOperationResult run(OwnCloudClient client) {
  113. OCFile folder = getStorageManager().getFileByDecryptedRemotePath(path);
  114. if (folder == null) {
  115. throw new IllegalArgumentException("Trying to share on a null folder: " + path);
  116. }
  117. boolean isEncrypted = folder.isEncrypted();
  118. String token = null;
  119. long newCounter = folder.getE2eCounter() + 1;
  120. // E2E: lock folder
  121. if (isEncrypted) {
  122. try {
  123. String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
  124. if ("".equals(publicKey)) {
  125. NextcloudClient nextcloudClient = new ClientFactoryImpl(context).createNextcloudClient(user);
  126. RemoteOperationResult<String> result = new GetPublicKeyRemoteOperation(shareeName).execute(nextcloudClient);
  127. if (result.isSuccess()) {
  128. // store it
  129. EncryptionUtils.savePublicKey(
  130. user,
  131. result.getResultData(),
  132. shareeName,
  133. arbitraryDataProvider
  134. );
  135. } else {
  136. RemoteOperationResult e = new RemoteOperationResult(new IllegalStateException());
  137. e.setMessage(context.getString(R.string.secure_share_not_set_up));
  138. return e;
  139. }
  140. }
  141. token = EncryptionUtils.lockFolder(folder, client, newCounter);
  142. } catch (UploadException | ClientFactory.CreationException e) {
  143. return new RemoteOperationResult(e);
  144. }
  145. }
  146. CreateShareRemoteOperation operation = new CreateShareRemoteOperation(
  147. path,
  148. shareType,
  149. shareeName,
  150. false,
  151. sharePassword,
  152. permissions,
  153. noteMessage
  154. );
  155. operation.setGetShareDetails(true);
  156. RemoteOperationResult shareResult = operation.execute(client);
  157. if (!shareResult.isSuccess() || shareResult.getData().size() == 0) {
  158. // something went wrong
  159. return shareResult;
  160. }
  161. // E2E: update metadata
  162. if (isEncrypted) {
  163. Object object = EncryptionUtils.downloadFolderMetadata(folder,
  164. client,
  165. context,
  166. user
  167. );
  168. if (object instanceof DecryptedFolderMetadataFileV1) {
  169. throw new RuntimeException("Trying to share on e2e v1!");
  170. }
  171. DecryptedFolderMetadataFile metadata = (DecryptedFolderMetadataFile) object;
  172. boolean metadataExists;
  173. if (metadata == null) {
  174. String cert = EncryptionUtils.retrievePublicKeyForUser(user, context);
  175. metadata = new EncryptionUtilsV2().createDecryptedFolderMetadataFile();
  176. metadata.getUsers().add(new DecryptedUser(client.getUserId(), cert));
  177. metadataExists = false;
  178. } else {
  179. metadataExists = true;
  180. }
  181. EncryptionUtilsV2 encryptionUtilsV2 = new EncryptionUtilsV2();
  182. // add sharee to metadata
  183. String publicKey = EncryptionUtils.getPublicKey(user, shareeName, arbitraryDataProvider);
  184. DecryptedFolderMetadataFile newMetadata = encryptionUtilsV2.addShareeToMetadata(metadata,
  185. shareeName,
  186. publicKey);
  187. // upload metadata
  188. metadata.getMetadata().setCounter(newCounter);
  189. try {
  190. encryptionUtilsV2.serializeAndUploadMetadata(folder,
  191. newMetadata,
  192. token,
  193. client,
  194. metadataExists,
  195. context,
  196. user,
  197. getStorageManager());
  198. } catch (UploadException e) {
  199. return new RemoteOperationResult<>(new RuntimeException("Uploading metadata failed"));
  200. }
  201. // E2E: unlock folder
  202. RemoteOperationResult<Void> unlockResult = EncryptionUtils.unlockFolder(folder, client, token);
  203. if (!unlockResult.isSuccess()) {
  204. return new RemoteOperationResult<>(new RuntimeException("Unlock failed"));
  205. }
  206. }
  207. OCShare share = (OCShare) shareResult.getData().get(0);
  208. // once creating share link update other information
  209. UpdateShareInfoOperation updateShareInfoOperation = new UpdateShareInfoOperation(share, getStorageManager());
  210. updateShareInfoOperation.setExpirationDateInMillis(expirationDateInMillis);
  211. updateShareInfoOperation.setHideFileDownload(hideFileDownload);
  212. updateShareInfoOperation.setNote(noteMessage);
  213. updateShareInfoOperation.setLabel(label);
  214. //update permissions for external share (will otherwise default to read-only)
  215. updateShareInfoOperation.setPermissions(permissions);
  216. // execute and save the result in database
  217. RemoteOperationResult updateShareInfoResult = updateShareInfoOperation.execute(client);
  218. if (updateShareInfoResult.isSuccess() && updateShareInfoResult.getData().size() > 0) {
  219. OCShare shareUpdated = (OCShare) updateShareInfoResult.getData().get(0);
  220. updateData(shareUpdated);
  221. }
  222. return shareResult;
  223. }
  224. private void updateData(OCShare share) {
  225. // Update DB with the response
  226. share.setPath(path);
  227. share.setFolder(path.endsWith(FileUtils.PATH_SEPARATOR));
  228. share.setPasswordProtected(!TextUtils.isEmpty(sharePassword));
  229. getStorageManager().saveShare(share);
  230. // Update OCFile with data from share: ShareByLink and publicLink
  231. OCFile file = getStorageManager().getFileByPath(path);
  232. if (file != null) {
  233. file.setSharedWithSharee(true); // TODO - this should be done by the FileContentProvider, as part of getStorageManager().saveShare(share)
  234. getStorageManager().saveFile(file);
  235. }
  236. }
  237. public String getPath() {
  238. return this.path;
  239. }
  240. public void setLabel(String label) {
  241. this.label = label;
  242. }
  243. }