CreateFolderOperation.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * @author masensio
  6. * Copyright (C) 2015 ownCloud Inc.
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License version 2,
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. package com.owncloud.android.operations;
  22. import android.content.Context;
  23. import android.util.Pair;
  24. import com.nextcloud.client.account.User;
  25. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  26. import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
  27. import com.owncloud.android.datamodel.DecryptedFolderMetadata;
  28. import com.owncloud.android.datamodel.EncryptedFolderMetadata;
  29. import com.owncloud.android.datamodel.FileDataStorageManager;
  30. import com.owncloud.android.datamodel.OCFile;
  31. import com.owncloud.android.lib.common.OwnCloudClient;
  32. import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
  33. import com.owncloud.android.lib.common.operations.RemoteOperation;
  34. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  35. import com.owncloud.android.lib.common.utils.Log_OC;
  36. import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
  37. import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
  38. import com.owncloud.android.lib.resources.files.ReadFolderRemoteOperation;
  39. import com.owncloud.android.lib.resources.files.model.RemoteFile;
  40. import com.owncloud.android.operations.common.SyncOperation;
  41. import com.owncloud.android.utils.EncryptionUtils;
  42. import com.owncloud.android.utils.FileStorageUtils;
  43. import com.owncloud.android.utils.MimeType;
  44. import java.io.File;
  45. import java.util.UUID;
  46. import androidx.annotation.NonNull;
  47. import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
  48. import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
  49. /**
  50. * Access to remote operation performing the creation of a new folder in the ownCloud server.
  51. * Save the new folder in Database.
  52. */
  53. public class CreateFolderOperation extends SyncOperation implements OnRemoteOperationListener {
  54. private static final String TAG = CreateFolderOperation.class.getSimpleName();
  55. protected String remotePath;
  56. private RemoteFile createdRemoteFolder;
  57. private User user;
  58. private Context context;
  59. /**
  60. * Constructor
  61. */
  62. public CreateFolderOperation(String remotePath, User user, Context context, FileDataStorageManager storageManager) {
  63. super(storageManager);
  64. this.remotePath = remotePath;
  65. this.user = user;
  66. this.context = context;
  67. }
  68. @Override
  69. protected RemoteOperationResult run(OwnCloudClient client) {
  70. String remoteParentPath = new File(getRemotePath()).getParent();
  71. remoteParentPath = remoteParentPath.endsWith(PATH_SEPARATOR) ?
  72. remoteParentPath : remoteParentPath + PATH_SEPARATOR;
  73. OCFile parent = getStorageManager().getFileByDecryptedRemotePath(remoteParentPath);
  74. String tempRemoteParentPath = remoteParentPath;
  75. while (parent == null) {
  76. tempRemoteParentPath = new File(tempRemoteParentPath).getParent();
  77. if (!tempRemoteParentPath.endsWith(PATH_SEPARATOR)) {
  78. tempRemoteParentPath = tempRemoteParentPath + PATH_SEPARATOR;
  79. }
  80. parent = getStorageManager().getFileByDecryptedRemotePath(tempRemoteParentPath);
  81. }
  82. // check if any parent is encrypted
  83. boolean encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager());
  84. if (encryptedAncestor) {
  85. return encryptedCreate(parent, client);
  86. } else {
  87. return normalCreate(client);
  88. }
  89. }
  90. private RemoteOperationResult encryptedCreate(OCFile parent, OwnCloudClient client) {
  91. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
  92. String privateKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PRIVATE_KEY);
  93. String publicKey = arbitraryDataProvider.getValue(user.getAccountName(), EncryptionUtils.PUBLIC_KEY);
  94. String token = null;
  95. Boolean metadataExists;
  96. DecryptedFolderMetadata metadata;
  97. String encryptedRemotePath = null;
  98. String filename = new File(remotePath).getName();
  99. try {
  100. // lock folder
  101. token = EncryptionUtils.lockFolder(parent, client);
  102. // get metadata
  103. Pair<Boolean, DecryptedFolderMetadata> metadataPair = EncryptionUtils.retrieveMetadata(parent,
  104. client,
  105. privateKey,
  106. publicKey,
  107. arbitraryDataProvider,
  108. user);
  109. metadataExists = metadataPair.first;
  110. metadata = metadataPair.second;
  111. // check if filename already exists
  112. if (isFileExisting(metadata, filename)) {
  113. return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
  114. }
  115. // generate new random file name, check if it exists in metadata
  116. String encryptedFileName = createRandomFileName(metadata);
  117. encryptedRemotePath = parent.getRemotePath() + encryptedFileName;
  118. RemoteOperationResult result = new CreateFolderRemoteOperation(encryptedRemotePath,
  119. true,
  120. token)
  121. .execute(client);
  122. if (result.isSuccess()) {
  123. // update metadata
  124. metadata.getFiles().put(encryptedFileName, createDecryptedFile(filename));
  125. EncryptedFolderMetadata encryptedFolderMetadata = EncryptionUtils.encryptFolderMetadata(metadata,
  126. privateKey,
  127. publicKey,
  128. arbitraryDataProvider,
  129. user,
  130. parent.getLocalId());
  131. String serializedFolderMetadata = EncryptionUtils.serializeJSON(encryptedFolderMetadata);
  132. // upload metadata
  133. EncryptionUtils.uploadMetadata(parent,
  134. serializedFolderMetadata,
  135. token,
  136. client,
  137. metadataExists);
  138. // unlock folder
  139. if (token != null) {
  140. RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
  141. if (unlockFolderResult.isSuccess()) {
  142. token = null;
  143. } else {
  144. // TODO do better
  145. throw new RuntimeException("Could not unlock folder!");
  146. }
  147. }
  148. RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(encryptedRemotePath)
  149. .execute(client);
  150. createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
  151. OCFile newDir = createRemoteFolderOcFile(parent, filename, createdRemoteFolder);
  152. getStorageManager().saveFile(newDir);
  153. RemoteOperationResult encryptionOperationResult = new ToggleEncryptionRemoteOperation(
  154. newDir.getLocalId(),
  155. newDir.getRemotePath(),
  156. true)
  157. .execute(client);
  158. if (!encryptionOperationResult.isSuccess()) {
  159. throw new RuntimeException("Error creating encrypted subfolder!");
  160. }
  161. } else {
  162. // revert to sane state in case of any error
  163. Log_OC.e(TAG, remotePath + " hasn't been created");
  164. }
  165. return result;
  166. } catch (Exception e) {
  167. if (!EncryptionUtils.unlockFolder(parent, client, token).isSuccess()) {
  168. throw new RuntimeException("Could not clean up after failing folder creation!", e);
  169. }
  170. // remove folder
  171. if (encryptedRemotePath != null) {
  172. RemoteOperationResult removeResult = new RemoveRemoteEncryptedFileOperation(encryptedRemotePath,
  173. parent.getLocalId(),
  174. user,
  175. context,
  176. filename).execute(client);
  177. if (!removeResult.isSuccess()) {
  178. throw new RuntimeException("Could not clean up after failing folder creation!");
  179. }
  180. }
  181. // TODO do better
  182. return new RemoteOperationResult(e);
  183. } finally {
  184. // unlock folder
  185. if (token != null) {
  186. RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parent, client, token);
  187. if (!unlockFolderResult.isSuccess()) {
  188. // TODO do better
  189. throw new RuntimeException("Could not unlock folder!");
  190. }
  191. }
  192. }
  193. }
  194. private boolean isFileExisting(DecryptedFolderMetadata metadata, String filename) {
  195. for (String key : metadata.getFiles().keySet()) {
  196. DecryptedFolderMetadata.DecryptedFile file = metadata.getFiles().get(key);
  197. if (file != null && filename.equalsIgnoreCase(file.getEncrypted().getFilename())) {
  198. return true;
  199. }
  200. }
  201. return false;
  202. }
  203. @NonNull
  204. private OCFile createRemoteFolderOcFile(OCFile parent, String filename, RemoteFile remoteFolder) {
  205. OCFile newDir = new OCFile(remoteFolder.getRemotePath());
  206. newDir.setMimeType(MimeType.DIRECTORY);
  207. newDir.setParentId(parent.getFileId());
  208. newDir.setRemoteId(remoteFolder.getRemoteId());
  209. newDir.setModificationTimestamp(System.currentTimeMillis());
  210. newDir.setEncrypted(true);
  211. newDir.setPermissions(remoteFolder.getPermissions());
  212. newDir.setDecryptedRemotePath(parent.getDecryptedRemotePath() + filename + "/");
  213. return newDir;
  214. }
  215. @NonNull
  216. private DecryptedFolderMetadata.DecryptedFile createDecryptedFile(String filename) {
  217. // Key, always generate new one
  218. byte[] key = EncryptionUtils.generateKey();
  219. // IV, always generate new one
  220. byte[] iv = EncryptionUtils.randomBytes(EncryptionUtils.ivLength);
  221. DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
  222. DecryptedFolderMetadata.Data data = new DecryptedFolderMetadata.Data();
  223. data.setFilename(filename);
  224. data.setMimetype(MimeType.WEBDAV_FOLDER);
  225. data.setKey(EncryptionUtils.encodeBytesToBase64String(key));
  226. decryptedFile.setEncrypted(data);
  227. decryptedFile.setInitializationVector(EncryptionUtils.encodeBytesToBase64String(iv));
  228. return decryptedFile;
  229. }
  230. @NonNull
  231. private String createRandomFileName(DecryptedFolderMetadata metadata) {
  232. String encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
  233. while (metadata.getFiles().get(encryptedFileName) != null) {
  234. encryptedFileName = UUID.randomUUID().toString().replaceAll("-", "");
  235. }
  236. return encryptedFileName;
  237. }
  238. private RemoteOperationResult normalCreate(OwnCloudClient client) {
  239. RemoteOperationResult result = new CreateFolderRemoteOperation(remotePath, true).execute(client);
  240. if (result.isSuccess()) {
  241. RemoteOperationResult remoteFolderOperationResult = new ReadFolderRemoteOperation(remotePath)
  242. .execute(client);
  243. createdRemoteFolder = (RemoteFile) remoteFolderOperationResult.getData().get(0);
  244. saveFolderInDB();
  245. } else {
  246. Log_OC.e(TAG, remotePath + " hasn't been created");
  247. }
  248. return result;
  249. }
  250. @Override
  251. public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
  252. if (operation instanceof CreateFolderRemoteOperation) {
  253. onCreateRemoteFolderOperationFinish(result);
  254. }
  255. }
  256. private void onCreateRemoteFolderOperationFinish(RemoteOperationResult result) {
  257. if (result.isSuccess()) {
  258. saveFolderInDB();
  259. } else {
  260. Log_OC.e(TAG, remotePath + " hasn't been created");
  261. }
  262. }
  263. /**
  264. * Save new directory in local database.
  265. */
  266. private void saveFolderInDB() {
  267. if (getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) {
  268. // When parent of remote path is not created
  269. String[] subFolders = remotePath.split(PATH_SEPARATOR);
  270. String composedRemotePath = ROOT_PATH;
  271. // For each ancestor folders create them recursively
  272. for (String subFolder : subFolders) {
  273. if (!subFolder.isEmpty()) {
  274. composedRemotePath = composedRemotePath + subFolder + PATH_SEPARATOR;
  275. remotePath = composedRemotePath;
  276. saveFolderInDB();
  277. }
  278. }
  279. } else { // Create directory on DB
  280. OCFile newDir = new OCFile(remotePath);
  281. newDir.setMimeType(MimeType.DIRECTORY);
  282. long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId();
  283. newDir.setParentId(parentId);
  284. newDir.setRemoteId(createdRemoteFolder.getRemoteId());
  285. newDir.setModificationTimestamp(System.currentTimeMillis());
  286. newDir.setEncrypted(FileStorageUtils.checkEncryptionStatus(newDir, getStorageManager()));
  287. newDir.setPermissions(createdRemoteFolder.getPermissions());
  288. getStorageManager().saveFile(newDir);
  289. Log_OC.d(TAG, "Create directory " + remotePath + " in Database");
  290. }
  291. }
  292. public String getRemotePath() {
  293. return remotePath;
  294. }
  295. }