From 3695e620c87f2732b6bf029eedde07fec1522bc4 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 5 Dec 2024 14:41:30 +0100 Subject: [PATCH 01/19] impl draft based on FileSystemProperties --- src/main/java/module-info.java | 1 + .../cryptofs/CryptoFileSystemModule.java | 17 +++++++++ .../cryptofs/CryptoFileSystemProperties.java | 22 ++++++++++++ .../cryptofs/event/DecryptionFailedEvent.java | 23 ++++++++++++ .../cryptofs/event/FilesystemEvent.java | 36 +++++++++++++++++++ .../cryptofs/event/LockedEvent.java | 9 +++++ .../cryptomator/cryptofs/fh/ChunkLoader.java | 15 +++++++- .../cryptofs/fh/FileHeaderHolder.java | 13 ++++++- .../cryptofs/fh/ChunkLoaderTest.java | 2 +- .../cryptofs/fh/FileHeaderHolderTest.java | 2 +- 10 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java create mode 100644 src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java create mode 100644 src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index abe11b305..6848d33fa 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -22,6 +22,7 @@ requires java.compiler; exports org.cryptomator.cryptofs; + exports org.cryptomator.cryptofs.event; exports org.cryptomator.cryptofs.common; exports org.cryptomator.cryptofs.health.api; exports org.cryptomator.cryptofs.migration; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index eacc19722..717403db2 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -10,15 +10,21 @@ import org.cryptomator.cryptofs.attr.AttributeComponent; import org.cryptomator.cryptofs.attr.AttributeViewComponent; import org.cryptomator.cryptofs.dir.DirectoryStreamComponent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptofs.fh.OpenCryptoFileComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Named; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; +import java.util.concurrent.Flow; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.SubmissionPublisher; +import java.util.function.Consumer; @Module(subcomponents = {AttributeComponent.class, AttributeViewComponent.class, OpenCryptoFileComponent.class, DirectoryStreamComponent.class}) class CryptoFileSystemModule { @@ -35,4 +41,15 @@ public Optional provideNativeFileStore(@PathToVault Path pathToVault) return Optional.empty(); } } + + @Provides + @CryptoFileSystemScoped + @Named("Babadook") + public Consumer provideFilesystemEventPublisher(CryptoFileSystemProperties fsProps) { + SubmissionPublisher publisher = new SubmissionPublisher<>();//ForkJoinPool.commonPool(),200); //TODO: capacity? + if(fsProps.filesystemEventSubscriber() != null) { + publisher.subscribe(fsProps.filesystemEventSubscriber()); + } + return publisher::submit; + } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index fb79a344c..e0050901e 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -9,10 +9,12 @@ package org.cryptomator.cryptofs; import com.google.common.base.Strings; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.MasterkeyLoader; import java.net.URI; +import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.AbstractMap; @@ -20,6 +22,7 @@ import java.util.EnumSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.Flow; import java.util.function.Consumer; import static java.util.Arrays.asList; @@ -113,6 +116,7 @@ private CryptoFileSystemProperties(Builder builder) { Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // + Map.entry("fsSubscriber", builder.subscriber), // Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), // Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), // Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) // @@ -153,6 +157,10 @@ int shorteningThreshold() { return (int) get(PROPERTY_SHORTENING_THRESHOLD); } + Flow.Subscriber filesystemEventSubscriber() { + return (Flow.Subscriber) get("fsSubscriber"); + } + @Override public Set> entrySet() { return entries; @@ -208,6 +216,7 @@ public static class Builder { private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH; private int shorteningThreshold = DEFAULT_SHORTENING_THRESHOLD; + private Flow.Subscriber subscriber = null; private Builder() { } @@ -220,6 +229,7 @@ private Builder(Map properties) { checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength); checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold); checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); + checkedSet(Flow.Subscriber.class, "fsSubscriber", properties, this::withFilesystemEventSubscriber); } private void checkedSet(Class type, String key, Map properties, Consumer setter) { @@ -334,6 +344,18 @@ public Builder withMasterkeyFilename(String masterkeyFilename) { return this; } + /** + * Sets the subscriber for filesystem events. + * + * @param subscriber the subscriber to recieve filesystem events + * @return this + * @since 2.8.0 + */ + public Builder withFilesystemEventSubscriber(Flow.Subscriber subscriber) { + this.subscriber = subscriber; + return this; + } + /** * Validates the values and creates new {@link CryptoFileSystemProperties}. * diff --git a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java new file mode 100644 index 000000000..eb18402c9 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java @@ -0,0 +1,23 @@ +package org.cryptomator.cryptofs.event; + +import org.cryptomator.cryptolib.api.AuthenticationFailedException; + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; + +public class DecryptionFailedEvent extends FilesystemEvent { + + private final Path resource; + private final AuthenticationFailedException e; + + public DecryptionFailedEvent(Path resource, AuthenticationFailedException e) { + super(Type.DECRYPTION_FAILED); + this.resource = resource; + this.e = e; + } + + public Path getResource() { + return resource; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java new file mode 100644 index 000000000..285624ce3 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -0,0 +1,36 @@ +package org.cryptomator.cryptofs.event; + +public abstract class FilesystemEvent { + + private final Type type; + + protected FilesystemEvent(Type type) { + this.type = type; + } + + LockedEvent toLockedEvent() { + return toEvent(LockedEvent.class); + } + + DecryptionFailedEvent toDecryptionFailedEvent() { + return toEvent(DecryptionFailedEvent.class); + } + + T toEvent(Class clazz) { + try { + return clazz.cast(this); + } catch (ClassCastException e) { + throw new IllegalCallerException(); + } + } + + public Type getType() { + return type; + } + + public enum Type { + DECRYPTION_FAILED, + LOCKED; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java new file mode 100644 index 000000000..4542d7587 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java @@ -0,0 +1,9 @@ +package org.cryptomator.cryptofs.event; + +public class LockedEvent extends FilesystemEvent { + + public LockedEvent() { + super(Type.LOCKED); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index e2fafb045..2ef4c49aa 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -1,16 +1,24 @@ package org.cryptomator.cryptofs.fh; import org.cryptomator.cryptofs.CryptoFileSystemStats; +import org.cryptomator.cryptofs.event.DecryptionFailedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import javax.inject.Inject; +import javax.inject.Named; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; @OpenFileScoped class ChunkLoader { + private final Consumer observer; + private final AtomicReference path; private final Cryptor cryptor; private final ChunkIO ciphertext; private final FileHeaderHolder headerHolder; @@ -18,7 +26,9 @@ class ChunkLoader { private final BufferPool bufferPool; @Inject - public ChunkLoader(Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { + public ChunkLoader(@Named("Babadook") Consumer observer, @CurrentOpenFilePath AtomicReference path, Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { + this.observer = observer; + this.path = path; this.cryptor = cryptor; this.ciphertext = ciphertext; this.headerHolder = headerHolder; @@ -42,6 +52,9 @@ public ByteBuffer load(Long chunkIndex) throws IOException, AuthenticationFailed stats.addBytesDecrypted(cleartextBuf.remaining()); } return cleartextBuf; + } catch (AuthenticationFailedException e) { + observer.accept(new DecryptionFailedEvent(path.get(), e)); + throw e; } finally { bufferPool.recycle(ciphertextBuf); } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index 822b8740d..ff5617e93 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -1,5 +1,8 @@ package org.cryptomator.cryptofs.fh; +import org.cryptomator.cryptofs.event.DecryptionFailedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; +import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileHeader; @@ -7,18 +10,21 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; @OpenFileScoped public class FileHeaderHolder { private static final Logger LOG = LoggerFactory.getLogger(FileHeaderHolder.class); + private final Consumer observer; private final Cryptor cryptor; private final AtomicReference path; private final AtomicReference header = new AtomicReference<>(); @@ -26,7 +32,8 @@ public class FileHeaderHolder { private final AtomicBoolean isPersisted = new AtomicBoolean(); @Inject - public FileHeaderHolder(Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { + public FileHeaderHolder(@Named("Babadook") Consumer observer, Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { + this.observer = observer; this.cryptor = cryptor; this.path = path; } @@ -74,6 +81,10 @@ FileHeader loadExisting(FileChannel ch) throws IOException { header.set(existingHeader); isPersisted.set(true); return existingHeader; + } catch (AuthenticationFailedException e) { + System.out.println("AuthenticationFailedException occured in cryptofs"); + observer.accept(new DecryptionFailedEvent(path.get(),e)); + throw new IOException("Unable to decrypt header of file " + path.get(), e); } catch (IllegalArgumentException | CryptoException e) { throw new IOException("Unable to decrypt header of file " + path.get(), e); } diff --git a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java index dac1d3039..68e4951b9 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java @@ -44,7 +44,7 @@ public class ChunkLoaderTest { private final FileHeader header = mock(FileHeader.class); private final FileHeaderHolder headerHolder = mock(FileHeaderHolder.class); private final BufferPool bufferPool = mock(BufferPool.class); - private final ChunkLoader inTest = new ChunkLoader(cryptor, chunkIO, headerHolder, stats, bufferPool); + private final ChunkLoader inTest = new ChunkLoader(null, null,cryptor, chunkIO, headerHolder, stats, bufferPool); @BeforeEach public void setup() throws IOException { diff --git a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java index ed93fac42..11f540b36 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java @@ -37,7 +37,7 @@ public class FileHeaderHolderTest { private final Path path = mock(Path.class, "openFile.txt"); private final AtomicReference pathRef = new AtomicReference<>(path); - private final FileHeaderHolder inTest = new FileHeaderHolder(cryptor, pathRef); + private final FileHeaderHolder inTest = new FileHeaderHolder(null, cryptor, pathRef); @BeforeEach public void setup() throws IOException { From e44c2e47e1044c64044067073af1b2d8ac3b37d0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 28 Jan 2025 20:21:13 +0100 Subject: [PATCH 02/19] simplified API --- .../cryptofs/CryptoFileSystemModule.java | 11 ++----- .../cryptofs/CryptoFileSystemProperties.java | 24 ++++++++++----- .../cryptomator/cryptofs/fh/ChunkLoader.java | 8 ++--- .../cryptofs/fh/FileHeaderHolder.java | 13 ++++----- .../CryptoFileSystemPropertiesTest.java | 17 ++++++----- .../cryptofs/fh/ChunkLoaderTest.java | 29 ++++++++++++++++++- .../cryptofs/fh/FileHeaderHolderTest.java | 21 ++++++++++++-- 7 files changed, 85 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 717403db2..8d861b47d 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -21,9 +21,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; -import java.util.concurrent.Flow; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.SubmissionPublisher; import java.util.function.Consumer; @Module(subcomponents = {AttributeComponent.class, AttributeViewComponent.class, OpenCryptoFileComponent.class, DirectoryStreamComponent.class}) @@ -45,11 +42,7 @@ public Optional provideNativeFileStore(@PathToVault Path pathToVault) @Provides @CryptoFileSystemScoped @Named("Babadook") - public Consumer provideFilesystemEventPublisher(CryptoFileSystemProperties fsProps) { - SubmissionPublisher publisher = new SubmissionPublisher<>();//ForkJoinPool.commonPool(),200); //TODO: capacity? - if(fsProps.filesystemEventSubscriber() != null) { - publisher.subscribe(fsProps.filesystemEventSubscriber()); - } - return publisher::submit; + public Consumer provideFilesystemEventConsumer(CryptoFileSystemProperties fsProps) { + return (Consumer) fsProps.get(CryptoFileSystemProperties.PROPERTY_NOTIFY_METHOD); } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index e0050901e..dc417cbe9 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -14,7 +14,6 @@ import org.cryptomator.cryptolib.api.MasterkeyLoader; import java.net.URI; -import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.AbstractMap; @@ -83,6 +82,15 @@ public class CryptoFileSystemProperties extends AbstractMap { static final String DEFAULT_MASTERKEY_FILENAME = "masterkey.cryptomator"; + /** + * Key identifying the function to call for notifications. + * + * @since 2.9.0 + */ + public static final String PROPERTY_NOTIFY_METHOD = "notificationConsumer"; + + static final Consumer DEFAULT_NOTIFY_METHOD = (FilesystemEvent e) -> {} ; + /** * Key identifying the filesystem flags. * @@ -116,7 +124,7 @@ private CryptoFileSystemProperties(Builder builder) { Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // - Map.entry("fsSubscriber", builder.subscriber), // + Map.entry(PROPERTY_NOTIFY_METHOD, builder.eventConsumer), // Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), // Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), // Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) // @@ -216,7 +224,7 @@ public static class Builder { private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH; private int shorteningThreshold = DEFAULT_SHORTENING_THRESHOLD; - private Flow.Subscriber subscriber = null; + private Consumer eventConsumer = DEFAULT_NOTIFY_METHOD; private Builder() { } @@ -229,7 +237,7 @@ private Builder(Map properties) { checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength); checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold); checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); - checkedSet(Flow.Subscriber.class, "fsSubscriber", properties, this::withFilesystemEventSubscriber); + checkedSet(Consumer.class, PROPERTY_NOTIFY_METHOD, properties, this::withFilesystemEventConsumer); } private void checkedSet(Class type, String key, Map properties, Consumer setter) { @@ -345,14 +353,14 @@ public Builder withMasterkeyFilename(String masterkeyFilename) { } /** - * Sets the subscriber for filesystem events. + * Sets the consumer for filesystem events * - * @param subscriber the subscriber to recieve filesystem events + * @param eventConsumer the consumer to receive filesystem events * @return this * @since 2.8.0 */ - public Builder withFilesystemEventSubscriber(Flow.Subscriber subscriber) { - this.subscriber = subscriber; + public Builder withFilesystemEventConsumer(Consumer eventConsumer) { + this.eventConsumer = eventConsumer; return this; } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index 2ef4c49aa..28ccfaa05 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -17,7 +17,7 @@ @OpenFileScoped class ChunkLoader { - private final Consumer observer; + private final Consumer eventConsumer; private final AtomicReference path; private final Cryptor cryptor; private final ChunkIO ciphertext; @@ -26,8 +26,8 @@ class ChunkLoader { private final BufferPool bufferPool; @Inject - public ChunkLoader(@Named("Babadook") Consumer observer, @CurrentOpenFilePath AtomicReference path, Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { - this.observer = observer; + public ChunkLoader(@Named("Babadook") Consumer eventConsumer, @CurrentOpenFilePath AtomicReference path, Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { + this.eventConsumer = eventConsumer; this.path = path; this.cryptor = cryptor; this.ciphertext = ciphertext; @@ -53,7 +53,7 @@ public ByteBuffer load(Long chunkIndex) throws IOException, AuthenticationFailed } return cleartextBuf; } catch (AuthenticationFailedException e) { - observer.accept(new DecryptionFailedEvent(path.get(), e)); + eventConsumer.accept(new DecryptionFailedEvent(path.get(), e)); throw e; } finally { bufferPool.recycle(ciphertextBuf); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index ff5617e93..e8545b91f 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -24,7 +24,7 @@ public class FileHeaderHolder { private static final Logger LOG = LoggerFactory.getLogger(FileHeaderHolder.class); - private final Consumer observer; + private final Consumer eventConsumer; private final Cryptor cryptor; private final AtomicReference path; private final AtomicReference header = new AtomicReference<>(); @@ -32,8 +32,8 @@ public class FileHeaderHolder { private final AtomicBoolean isPersisted = new AtomicBoolean(); @Inject - public FileHeaderHolder(@Named("Babadook") Consumer observer, Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { - this.observer = observer; + public FileHeaderHolder(@Named("Babadook") Consumer eventConsumer, Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { + this.eventConsumer = eventConsumer; this.cryptor = cryptor; this.path = path; } @@ -81,11 +81,10 @@ FileHeader loadExisting(FileChannel ch) throws IOException { header.set(existingHeader); isPersisted.set(true); return existingHeader; - } catch (AuthenticationFailedException e) { - System.out.println("AuthenticationFailedException occured in cryptofs"); - observer.accept(new DecryptionFailedEvent(path.get(),e)); - throw new IOException("Unable to decrypt header of file " + path.get(), e); } catch (IllegalArgumentException | CryptoException e) { + if (e instanceof AuthenticationFailedException afe) { + eventConsumer.accept(new DecryptionFailedEvent(path.get(), afe)); + } throw new IOException("Unable to decrypt header of file " + path.get(), e); } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 34ad89cac..9690ead3f 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -51,7 +51,8 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // + anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); } @Test @@ -77,7 +78,8 @@ public void testFromMap() { anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, 255), // anEntry(PROPERTY_SHORTENING_THRESHOLD, 221), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // + anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); } @Test @@ -99,7 +101,8 @@ public void testWrapMapWithTrueReadonly() { anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)))); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // + anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); } @Test @@ -121,7 +124,8 @@ public void testWrapMapWithFalseReadonly() { anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)))); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)), // + anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); } @Test @@ -173,9 +177,8 @@ public void testWrapMapWithoutReadonly() { anEntry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, DEFAULT_MAX_CLEARTEXT_NAME_LENGTH), // anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // - anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)) - ) - ); + anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)), // + anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java index 68e4951b9..c54fe4151 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/ChunkLoaderTest.java @@ -1,6 +1,8 @@ package org.cryptomator.cryptofs.fh; import org.cryptomator.cryptofs.CryptoFileSystemStats; +import org.cryptomator.cryptofs.event.DecryptionFailedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptofs.matchers.ByteBufferMatcher; import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; @@ -11,11 +13,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.mockito.stubbing.Answer; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.function.Supplier; import static org.cryptomator.cryptofs.matchers.ByteBufferMatcher.contains; @@ -25,6 +32,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +52,9 @@ public class ChunkLoaderTest { private final FileHeader header = mock(FileHeader.class); private final FileHeaderHolder headerHolder = mock(FileHeaderHolder.class); private final BufferPool bufferPool = mock(BufferPool.class); - private final ChunkLoader inTest = new ChunkLoader(null, null,cryptor, chunkIO, headerHolder, stats, bufferPool); + private final Consumer eventConsumer = mock(Consumer.class); + private final AtomicReference filePath = new AtomicReference<>(mock(Path.class, "The filepath")); + private final ChunkLoader inTest = new ChunkLoader(eventConsumer, filePath,cryptor, chunkIO, headerHolder, stats, bufferPool); @BeforeEach public void setup() throws IOException { @@ -122,6 +132,23 @@ public void testLoadReturnsDecrytedDataNearEOF() throws IOException, Authenticat Assertions.assertEquals(CLEARTEXT_CHUNK_SIZE, data.capacity()); } + @Test + @DisplayName("load() rethrows on failed decryption the exception and creates an event") + public void testLoadThrowsAndNotifiesOnFailedDecryption() throws IOException { + long chunkIndex = 482L; + long chunkOffset = chunkIndex * CIPHERTEXT_CHUNK_SIZE + HEADER_SIZE; + when(chunkIO.read(argThat(hasAtLeastRemaining(CIPHERTEXT_CHUNK_SIZE)), eq(chunkOffset))).then(fillBufferWith((byte) 3, CIPHERTEXT_CHUNK_SIZE)); + doThrow(new AuthenticationFailedException("FAIL")) // + .when(fileContentCryptor).decryptChunk( // + argThat(contains(repeat(3).times(CIPHERTEXT_CHUNK_SIZE).asByteBuffer())), // + Mockito.any(), eq(chunkIndex), eq(header), eq(true) // + ); + + Assertions.assertThrows(AuthenticationFailedException.class, () -> inTest.load(chunkIndex)); + var isDecryptionFailedEvent = (ArgumentMatcher) ev -> ev instanceof DecryptionFailedEvent; + verify(eventConsumer).accept(ArgumentMatchers.argThat(isDecryptionFailedEvent)); + } + private Answer fillBufferWith(byte value, int amount) { return invocation -> { ByteBuffer buffer = invocation.getArgument(0); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java index 11f540b36..98f7d6374 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java @@ -1,5 +1,7 @@ package org.cryptomator.cryptofs.fh; +import org.cryptomator.cryptofs.event.DecryptionFailedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileHeader; @@ -10,6 +12,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.io.IOException; @@ -18,6 +22,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -36,13 +41,15 @@ public class FileHeaderHolderTest { private final Cryptor cryptor = mock(Cryptor.class); private final Path path = mock(Path.class, "openFile.txt"); private final AtomicReference pathRef = new AtomicReference<>(path); + private final Consumer eventConsumer = mock(Consumer.class); - private final FileHeaderHolder inTest = new FileHeaderHolder(null, cryptor, pathRef); + private FileHeaderHolder inTest; @BeforeEach public void setup() throws IOException { when(cryptor.fileHeaderCryptor()).thenReturn(fileHeaderCryptor); when(fileHeaderCryptor.encryptHeader(Mockito.any())).thenReturn(ByteBuffer.wrap(new byte[0])); + inTest = new FileHeaderHolder(eventConsumer, cryptor, pathRef); } @Nested @@ -66,7 +73,7 @@ public void setup() throws IOException, AuthenticationFailedException { } @Test - @DisplayName("load") + @DisplayName("load success") public void testLoadExisting() throws IOException, AuthenticationFailedException { FileHeader loadedHeader1 = inTest.loadExisting(channel); FileHeader loadedHeader2 = inTest.get(); @@ -81,6 +88,16 @@ public void testLoadExisting() throws IOException, AuthenticationFailedException Assertions.assertTrue(inTest.headerIsPersisted().get()); } + @Test + @DisplayName("load failure") + public void testLoadExistingFailure() { + Mockito.doThrow(AuthenticationFailedException.class).when(fileHeaderCryptor).decryptHeader(Mockito.any()); + + Assertions.assertThrows(IOException.class, () -> inTest.loadExisting(channel)); + var isDecryptionFailedEvent = (ArgumentMatcher) ev -> ev instanceof DecryptionFailedEvent; + verify(eventConsumer).accept(ArgumentMatchers.argThat(isDecryptionFailedEvent)); + } + } @Nested From fecb0a2dadd79645b8233f16668c0360dd23b344 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 28 Jan 2025 21:02:22 +0100 Subject: [PATCH 03/19] use records --- .../cryptofs/event/DecryptionFailedEvent.java | 24 ++++++-------- .../cryptofs/event/FilesystemEvent.java | 31 +++++++------------ .../cryptofs/event/LockedEvent.java | 8 ++--- .../cryptomator/cryptofs/fh/ChunkLoader.java | 2 +- .../cryptofs/fh/FileHeaderHolder.java | 2 +- 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java index eb18402c9..53e892e61 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java @@ -3,21 +3,17 @@ import org.cryptomator.cryptolib.api.AuthenticationFailedException; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicReference; -public class DecryptionFailedEvent extends FilesystemEvent { +/** + * Created, if decryption fails. + * @param ciphertextPath + * @param cleartextPath might be null + * @param e + */ +public record DecryptionFailedEvent(Path ciphertextPath, Path cleartextPath, AuthenticationFailedException e) implements FilesystemEvent { - private final Path resource; - private final AuthenticationFailedException e; - - public DecryptionFailedEvent(Path resource, AuthenticationFailedException e) { - super(Type.DECRYPTION_FAILED); - this.resource = resource; - this.e = e; - } - - public Path getResource() { - return resource; + @Override + public Type getType() { + return Type.DECRYPTION_FAILED; } - } diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java index 285624ce3..2f72e3304 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -1,35 +1,28 @@ package org.cryptomator.cryptofs.event; -public abstract class FilesystemEvent { +public interface FilesystemEvent { - private final Type type; - - protected FilesystemEvent(Type type) { - this.type = type; + static LockedEvent toLockedEvent(T fse) { + return toEvent(fse, LockedEvent.class); } - LockedEvent toLockedEvent() { - return toEvent(LockedEvent.class); + static DecryptionFailedEvent toDecryptionFailedEvent(T fse) throws ClassCastException { + return toEvent(fse, DecryptionFailedEvent.class); } - DecryptionFailedEvent toDecryptionFailedEvent() { - return toEvent(DecryptionFailedEvent.class); + static ConflictResolvedEvent toConflictResolvedEvent(T fse) throws ClassCastException { + return toEvent(fse, ConflictResolvedEvent.class); } - T toEvent(Class clazz) { - try { - return clazz.cast(this); - } catch (ClassCastException e) { - throw new IllegalCallerException(); - } + static T toEvent(U o, Class clazz) throws ClassCastException { + return clazz.cast(o); } - public Type getType() { - return type; - } + Type getType(); - public enum Type { + enum Type { DECRYPTION_FAILED, + CONFLICT_RESOLVED, LOCKED; } diff --git a/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java index 4542d7587..f6d2a2551 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java @@ -1,9 +1,9 @@ package org.cryptomator.cryptofs.event; -public class LockedEvent extends FilesystemEvent { +public record LockedEvent() implements FilesystemEvent { - public LockedEvent() { - super(Type.LOCKED); + @Override + public Type getType() { + return Type.LOCKED; } - } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index 28ccfaa05..ed646a7d3 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -53,7 +53,7 @@ public ByteBuffer load(Long chunkIndex) throws IOException, AuthenticationFailed } return cleartextBuf; } catch (AuthenticationFailedException e) { - eventConsumer.accept(new DecryptionFailedEvent(path.get(), e)); + eventConsumer.accept(new DecryptionFailedEvent(path.get(), null, e)); throw e; } finally { bufferPool.recycle(ciphertextBuf); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index e8545b91f..cea429fbd 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -83,7 +83,7 @@ FileHeader loadExisting(FileChannel ch) throws IOException { return existingHeader; } catch (IllegalArgumentException | CryptoException e) { if (e instanceof AuthenticationFailedException afe) { - eventConsumer.accept(new DecryptionFailedEvent(path.get(), afe)); + eventConsumer.accept(new DecryptionFailedEvent(path.get(), null, afe)); } throw new IOException("Unable to decrypt header of file " + path.get(), e); } From 1689c581426a8e6f79391c6e3bbfbb298d91760d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 29 Jan 2025 15:34:38 +0100 Subject: [PATCH 04/19] add conflictResolved event --- .../cryptofs/CryptoFileSystemModule.java | 1 - .../cryptofs/dir/C9rConflictResolver.java | 12 +++++++++++- .../event/ConflictResolutionFailedEvent.java | 5 +++++ .../cryptofs/event/ConflictResolvedEvent.java | 11 +++++++++++ .../cryptomator/cryptofs/fh/ChunkLoader.java | 2 +- .../cryptofs/fh/FileHeaderHolder.java | 2 +- .../cryptofs/dir/C9rConflictResolverTest.java | 19 ++++++++++++++++--- 7 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java create mode 100644 src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 8d861b47d..8ae0842f4 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -41,7 +41,6 @@ public Optional provideNativeFileStore(@PathToVault Path pathToVault) @Provides @CryptoFileSystemScoped - @Named("Babadook") public Consumer provideFilesystemEventConsumer(CryptoFileSystemProperties fsProps) { return (Consumer) fsProps.get(CryptoFileSystemProperties.PROPERTY_NOTIFY_METHOD); } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 4a7db9464..bb6eca978 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -6,6 +6,8 @@ import com.google.common.io.RecursiveDeleteOption; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptofs.event.ConflictResolvedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.Cryptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,10 +16,12 @@ import javax.inject.Named; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; import java.util.stream.Stream; import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME; @@ -33,14 +37,18 @@ class C9rConflictResolver { private final Cryptor cryptor; private final byte[] dirId; private final int maxC9rFileNameLength; + private final Path cleartextPath; private final int maxCleartextFileNameLength; + private final Consumer eventConsumer; @Inject - public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, VaultConfig vaultConfig) { + public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, VaultConfig vaultConfig, Consumer eventConsumer, @Named("cleartextPath") Path cleartextPath) { this.cryptor = cryptor; this.dirId = dirId.getBytes(StandardCharsets.US_ASCII); this.maxC9rFileNameLength = vaultConfig.getShorteningThreshold(); + this.cleartextPath = cleartextPath; this.maxCleartextFileNameLength = (maxC9rFileNameLength - 4) / 4 * 3 - 16; // math from FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength() + this.eventConsumer = eventConsumer; } public Stream process(Node node) { @@ -62,6 +70,7 @@ public Stream process(Node node) { return resolveConflict(node, canonicalPath); } catch (IOException e) { LOG.error("Failed to resolve conflict for " + node.ciphertextPath, e); + //TODO: notify! return Stream.empty(); } } @@ -111,6 +120,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str Node node = new Node(alternativePath); node.cleartextName = alternativeCleartext; node.extractedCiphertext = alternativeCiphertext; + eventConsumer.accept(new ConflictResolvedEvent(cleartextPath.resolve(cleartext), canonicalPath, cleartextPath.resolve(alternativeCleartext),alternativePath)); return node; } diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java new file mode 100644 index 000000000..07ce73106 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java @@ -0,0 +1,5 @@ +package org.cryptomator.cryptofs.event; + +public class ConflictResolutionFailedEvent { + +} diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java new file mode 100644 index 000000000..d8bc1b3bd --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java @@ -0,0 +1,11 @@ +package org.cryptomator.cryptofs.event; + +import java.nio.file.Path; + +public record ConflictResolvedEvent(Path cleartextPath, Path ciphertextPath, Path oldVersionCleartextPath, Path oldVersionCiphertextPath) implements FilesystemEvent{ + + @Override + public Type getType() { + return Type.CONFLICT_RESOLVED; + } +} diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index ed646a7d3..92b88274a 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -26,7 +26,7 @@ class ChunkLoader { private final BufferPool bufferPool; @Inject - public ChunkLoader(@Named("Babadook") Consumer eventConsumer, @CurrentOpenFilePath AtomicReference path, Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { + public ChunkLoader(Consumer eventConsumer, @CurrentOpenFilePath AtomicReference path, Cryptor cryptor, ChunkIO ciphertext, FileHeaderHolder headerHolder, CryptoFileSystemStats stats, BufferPool bufferPool) { this.eventConsumer = eventConsumer; this.path = path; this.cryptor = cryptor; diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index cea429fbd..3ed5f4cd8 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -32,7 +32,7 @@ public class FileHeaderHolder { private final AtomicBoolean isPersisted = new AtomicBoolean(); @Inject - public FileHeaderHolder(@Named("Babadook") Consumer eventConsumer, Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { + public FileHeaderHolder(Consumer eventConsumer, Cryptor cryptor, @CurrentOpenFilePath AtomicReference path) { this.eventConsumer = eventConsumer; this.cryptor = cryptor; this.path = path; diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index fc868950b..2c3f4dd5a 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -1,6 +1,8 @@ package org.cryptomator.cryptofs.dir; import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.event.ConflictResolvedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileNameCryptor; import org.junit.jupiter.api.Assertions; @@ -8,21 +10,27 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.function.Consumer; import java.util.stream.Stream; +import static org.mockito.Mockito.verify; + public class C9rConflictResolverTest { private Cryptor cryptor; private FileNameCryptor fileNameCryptor; private VaultConfig vaultConfig; + private Consumer eventConsumer = Mockito.mock(Consumer.class); + private Path cleartextPath = Mockito.mock(Path.class, "/clear/text/path/"); private C9rConflictResolver conflictResolver; @BeforeEach @@ -32,9 +40,10 @@ public void setup() { vaultConfig = Mockito.mock(VaultConfig.class); Mockito.when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor); Mockito.when(vaultConfig.getShorteningThreshold()).thenReturn(44); // results in max cleartext size = 14 - conflictResolver = new C9rConflictResolver(cryptor, "foo", vaultConfig); + Mockito.when(cleartextPath.resolve(Mockito.anyString())).thenReturn(cleartextPath); + conflictResolver = new C9rConflictResolver(cryptor, "foo", vaultConfig, eventConsumer, cleartextPath); } - + @Test public void testResolveNonConflictingNode() { Node unresolved = new Node(Paths.get("foo.c9r")); @@ -75,6 +84,8 @@ public void testResolveConflictingFileByChoosingNewName(@TempDir Path dir) throw Assertions.assertEquals("bar (1).txt", resolved.cleartextName); Assertions.assertTrue(Files.exists(resolved.ciphertextPath)); Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); + var isConflictResolvedEvent = (ArgumentMatcher) ev -> ev instanceof ConflictResolvedEvent; + verify(eventConsumer).accept(ArgumentMatchers.argThat(isConflictResolvedEvent)); } @Test @@ -94,6 +105,8 @@ public void testResolveConflictingFileByChoosingNewLengthLimitedName(@TempDir Pa Assertions.assertEquals("hello (1).txt", resolved.cleartextName); Assertions.assertTrue(Files.exists(resolved.ciphertextPath)); Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); + var isConflictResolvedEvent = (ArgumentMatcher) ev -> ev instanceof ConflictResolvedEvent; + verify(eventConsumer).accept(ArgumentMatchers.argThat(isConflictResolvedEvent)); } @Test From 7a564fbd1c646adb7835c970b9d12e0120968dd9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 30 Jan 2025 10:29:50 +0100 Subject: [PATCH 05/19] add conflict failed event --- .../cryptofs/dir/C9rConflictResolver.java | 8 ++++--- .../event/ConflictResolutionFailedEvent.java | 8 ++++++- .../cryptofs/event/FilesystemEvent.java | 5 +++++ .../cryptofs/dir/C9rConflictResolverTest.java | 21 +++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index bb6eca978..548399b5a 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -6,6 +6,7 @@ import com.google.common.io.RecursiveDeleteOption; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; import org.cryptomator.cryptofs.event.ConflictResolvedEvent; import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.Cryptor; @@ -69,14 +70,15 @@ public Stream process(Node node) { Path canonicalPath = node.ciphertextPath.resolveSibling(canonicalCiphertextFileName); return resolveConflict(node, canonicalPath); } catch (IOException e) { - LOG.error("Failed to resolve conflict for " + node.ciphertextPath, e); - //TODO: notify! + eventConsumer.accept(new ConflictResolutionFailedEvent(cleartextPath.resolve(node.cleartextName), node.ciphertextPath.resolve(node.fullCiphertextFileName), e)); + LOG.error("Failed to resolve conflict for {}", node.ciphertextPath, e); return Stream.empty(); } } } - private Stream resolveConflict(Node conflicting, Path canonicalPath) throws IOException { + //visible for testing + Stream resolveConflict(Node conflicting, Path canonicalPath) throws IOException { Path conflictingPath = conflicting.ciphertextPath; if (resolveConflictTrivially(canonicalPath, conflictingPath)) { Node resolved = new Node(canonicalPath); diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java index 07ce73106..47bf4d69e 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java @@ -1,5 +1,11 @@ package org.cryptomator.cryptofs.event; -public class ConflictResolutionFailedEvent { +import java.nio.file.Path; +public record ConflictResolutionFailedEvent(Path cleartextPath, Path ciphertextPath , Exception reason) implements FilesystemEvent { + + @Override + public Type getType() { + return Type.CONFLICT_RESOLUTION_FAILED; + } } diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java index 2f72e3304..31d6b29cd 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -14,6 +14,10 @@ static ConflictResolvedEvent toConflictResolvedEvent return toEvent(fse, ConflictResolvedEvent.class); } + static ConflictResolutionFailedEvent toConflictResolutionFailedEvent(T fse) throws ClassCastException { + return toEvent(fse, ConflictResolutionFailedEvent.class); + } + static T toEvent(U o, Class clazz) throws ClassCastException { return clazz.cast(o); } @@ -23,6 +27,7 @@ static T toEvent(U o, Cla enum Type { DECRYPTION_FAILED, CONFLICT_RESOLVED, + CONFLICT_RESOLUTION_FAILED, LOCKED; } diff --git a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java index 2c3f4dd5a..d6fdc1e66 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/C9rConflictResolverTest.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs.dir; import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; import org.cryptomator.cryptofs.event.ConflictResolvedEvent; import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.Cryptor; @@ -163,4 +164,24 @@ public void testResolveConflictingSymlinkTrivially(@TempDir Path dir) throws IOE Assertions.assertFalse(Files.exists(unresolved.ciphertextPath)); } + @Test + public void testConflictResolutionFails(@TempDir Path dir) throws IOException { + var p1 = Files.createFile(dir.resolve("foo (1).c9r")); + var p2 = Files.createFile(dir.resolve("foo.c9r")); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn("baz"); + Node unresolved = new Node(dir.resolve("foo (1).c9r")); + unresolved.cleartextName = "bar.txt"; + unresolved.extractedCiphertext = "foo"; + + var conflictResolverSpy = Mockito.spy(conflictResolver); + Mockito.doThrow(IOException.class).when(conflictResolverSpy).resolveConflict(Mockito.any(), Mockito.any()); + + Stream result = Assertions.assertDoesNotThrow(() -> conflictResolverSpy.process(unresolved)); + Assertions.assertEquals(0, result.toList().size()); + Assertions.assertTrue(Files.exists(p1)); + Assertions.assertTrue(Files.exists(p2)); + var isConflictResolutionFailedEvent = (ArgumentMatcher) ev -> ev instanceof ConflictResolutionFailedEvent; + verify(eventConsumer).accept(ArgumentMatchers.argThat(isConflictResolutionFailedEvent)); + } + } \ No newline at end of file From c9425964a8cfe59003b70829435bf7f6aac80604 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 30 Jan 2025 12:25:56 +0100 Subject: [PATCH 06/19] clean up --- .../cryptomator/cryptofs/CryptoFileSystemProperties.java | 4 ---- .../java/org/cryptomator/cryptofs/event/LockedEvent.java | 9 --------- 2 files changed, 13 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index dc417cbe9..9a204f964 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -165,10 +165,6 @@ int shorteningThreshold() { return (int) get(PROPERTY_SHORTENING_THRESHOLD); } - Flow.Subscriber filesystemEventSubscriber() { - return (Flow.Subscriber) get("fsSubscriber"); - } - @Override public Set> entrySet() { return entries; diff --git a/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java deleted file mode 100644 index f6d2a2551..000000000 --- a/src/main/java/org/cryptomator/cryptofs/event/LockedEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.cryptomator.cryptofs.event; - -public record LockedEvent() implements FilesystemEvent { - - @Override - public Type getType() { - return Type.LOCKED; - } -} From e94ebc2a55f1c2ebce68d8805ffdafac79df5ca1 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 30 Jan 2025 12:26:35 +0100 Subject: [PATCH 07/19] use sealed interface instead of type enum --- .../event/ConflictResolutionFailedEvent.java | 5 --- .../cryptofs/event/ConflictResolvedEvent.java | 5 --- .../cryptofs/event/DecryptionFailedEvent.java | 5 --- .../cryptofs/event/FilesystemEvent.java | 31 +------------------ 4 files changed, 1 insertion(+), 45 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java index 47bf4d69e..352fc6cec 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java @@ -3,9 +3,4 @@ import java.nio.file.Path; public record ConflictResolutionFailedEvent(Path cleartextPath, Path ciphertextPath , Exception reason) implements FilesystemEvent { - - @Override - public Type getType() { - return Type.CONFLICT_RESOLUTION_FAILED; - } } diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java index d8bc1b3bd..134bd71f3 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java @@ -3,9 +3,4 @@ import java.nio.file.Path; public record ConflictResolvedEvent(Path cleartextPath, Path ciphertextPath, Path oldVersionCleartextPath, Path oldVersionCiphertextPath) implements FilesystemEvent{ - - @Override - public Type getType() { - return Type.CONFLICT_RESOLVED; - } } diff --git a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java index 53e892e61..8269e6906 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java @@ -11,9 +11,4 @@ * @param e */ public record DecryptionFailedEvent(Path ciphertextPath, Path cleartextPath, AuthenticationFailedException e) implements FilesystemEvent { - - @Override - public Type getType() { - return Type.DECRYPTION_FAILED; - } } diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java index 31d6b29cd..3da0e1aa0 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -1,34 +1,5 @@ package org.cryptomator.cryptofs.event; -public interface FilesystemEvent { - - static LockedEvent toLockedEvent(T fse) { - return toEvent(fse, LockedEvent.class); - } - - static DecryptionFailedEvent toDecryptionFailedEvent(T fse) throws ClassCastException { - return toEvent(fse, DecryptionFailedEvent.class); - } - - static ConflictResolvedEvent toConflictResolvedEvent(T fse) throws ClassCastException { - return toEvent(fse, ConflictResolvedEvent.class); - } - - static ConflictResolutionFailedEvent toConflictResolutionFailedEvent(T fse) throws ClassCastException { - return toEvent(fse, ConflictResolutionFailedEvent.class); - } - - static T toEvent(U o, Class clazz) throws ClassCastException { - return clazz.cast(o); - } - - Type getType(); - - enum Type { - DECRYPTION_FAILED, - CONFLICT_RESOLVED, - CONFLICT_RESOLUTION_FAILED, - LOCKED; - } +public sealed interface FilesystemEvent permits ConflictResolutionFailedEvent, ConflictResolvedEvent, DecryptionFailedEvent { } From 5e552d1e1dc016f9657a028449f4b77a4982e703 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 30 Jan 2025 12:47:41 +0100 Subject: [PATCH 08/19] doc doc doc --- .../event/ConflictResolutionFailedEvent.java | 10 ++++++++- .../cryptofs/event/ConflictResolvedEvent.java | 11 +++++++++- .../cryptofs/event/DecryptionFailedEvent.java | 12 +++++----- .../cryptofs/event/FilesystemEvent.java | 22 +++++++++++++++++++ .../cryptomator/cryptofs/fh/ChunkLoader.java | 2 +- .../cryptofs/fh/FileHeaderHolder.java | 2 +- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java index 352fc6cec..4a7af7ff2 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java @@ -2,5 +2,13 @@ import java.nio.file.Path; -public record ConflictResolutionFailedEvent(Path cleartextPath, Path ciphertextPath , Exception reason) implements FilesystemEvent { +/** + * Emitted, if the conflict resolution inside an encrypted directory failed + * + * @param cleartextPath path within the cryptographic filesystem + * @param ciphertextPath path to the encrypted resource with the broken filename + * @param reason exception, why the resolution failed + */ +public record ConflictResolutionFailedEvent(Path cleartextPath, Path ciphertextPath, Exception reason) implements FilesystemEvent { + } diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java index 134bd71f3..26e54d54a 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java @@ -2,5 +2,14 @@ import java.nio.file.Path; -public record ConflictResolvedEvent(Path cleartextPath, Path ciphertextPath, Path oldVersionCleartextPath, Path oldVersionCiphertextPath) implements FilesystemEvent{ +/** + * Emitted, if a conflict inside an encrypted directory was resolved + * + * @param cleartextPath path within the cryptographic filesystem + * @param ciphertextPath path to the encrypted resource + * @param oldVersionCleartextPath path within the cryptographic filesystem of the renamed resource + * @param oldVersionCiphertextPath path to the renamed, encrypted resource + */ +public record ConflictResolvedEvent(Path cleartextPath, Path ciphertextPath, Path oldVersionCleartextPath, Path oldVersionCiphertextPath) implements FilesystemEvent { + } diff --git a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java index 8269e6906..199197db9 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java @@ -5,10 +5,12 @@ import java.nio.file.Path; /** - * Created, if decryption fails. - * @param ciphertextPath - * @param cleartextPath might be null - * @param e + * Emitted, if a decryption operation fails. + * + * @param cleartextPath path within the cryptographic filesystem + * @param ciphertextPath path to the encrypted resource + * @param e thrown exception */ -public record DecryptionFailedEvent(Path ciphertextPath, Path cleartextPath, AuthenticationFailedException e) implements FilesystemEvent { +public record DecryptionFailedEvent(Path cleartextPath, Path ciphertextPath, AuthenticationFailedException e) implements FilesystemEvent { + } diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java index 3da0e1aa0..a2d7fb090 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -1,5 +1,27 @@ package org.cryptomator.cryptofs.event; +import java.util.function.Consumer; + +/** + * Common interface for all filesystem events. + *

+ * Events are emitted via the notification method set in the properties during filesystem creation, see {@link org.cryptomator.cryptofs.CryptoFileSystemProperties.Builder#withFilesystemEventConsumer(Consumer)}. + *

+ * To get a specific event type, use the enhanced switch pattern or typecasting in if-instance of, e.g. + * {@code + * FilesystemEvent fse; + * switch (fse) { + * case DecryptionFailedEvent dfe -> //do stuff + * case ConflictResolvedEvent cre -> //do other stuff + * //other cases + * } + * if( fse instanceof DecryptionFailedEvent dfe) { + * //do more stuff + * } + * }. + * + * @apiNote Events might have occured a long time ago in a galaxy far, far away... therefore, any feedback method is non-blocking and might fail due to changes in the filesystem. + */ public sealed interface FilesystemEvent permits ConflictResolutionFailedEvent, ConflictResolvedEvent, DecryptionFailedEvent { } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index 92b88274a..b8533887c 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -53,7 +53,7 @@ public ByteBuffer load(Long chunkIndex) throws IOException, AuthenticationFailed } return cleartextBuf; } catch (AuthenticationFailedException e) { - eventConsumer.accept(new DecryptionFailedEvent(path.get(), null, e)); + eventConsumer.accept(new DecryptionFailedEvent(null, path.get(), e)); throw e; } finally { bufferPool.recycle(ciphertextBuf); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index 3ed5f4cd8..d83b65f7c 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -83,7 +83,7 @@ FileHeader loadExisting(FileChannel ch) throws IOException { return existingHeader; } catch (IllegalArgumentException | CryptoException e) { if (e instanceof AuthenticationFailedException afe) { - eventConsumer.accept(new DecryptionFailedEvent(path.get(), null, afe)); + eventConsumer.accept(new DecryptionFailedEvent(null, path.get(), afe)); } throw new IOException("Unable to decrypt header of file " + path.get(), e); } From 311b86c49068497accc4d0fd9d411e32cf9331b5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 15:49:59 +0100 Subject: [PATCH 09/19] clean up --- .../java/org/cryptomator/cryptofs/CryptoFileSystemModule.java | 1 - .../org/cryptomator/cryptofs/CryptoFileSystemProperties.java | 3 +-- .../java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java | 3 +-- .../java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java | 1 - 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 8ae0842f4..376d300eb 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -15,7 +15,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Named; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.Files; diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 9a204f964..ec5a12e7c 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -21,7 +21,6 @@ import java.util.EnumSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.Flow; import java.util.function.Consumer; import static java.util.Arrays.asList; @@ -89,7 +88,7 @@ public class CryptoFileSystemProperties extends AbstractMap { */ public static final String PROPERTY_NOTIFY_METHOD = "notificationConsumer"; - static final Consumer DEFAULT_NOTIFY_METHOD = (FilesystemEvent e) -> {} ; + static final Consumer DEFAULT_NOTIFY_METHOD = (FilesystemEvent e) -> {}; /** * Key identifying the filesystem flags. diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 548399b5a..0f86fd790 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -17,7 +17,6 @@ import javax.inject.Named; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -122,7 +121,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str Node node = new Node(alternativePath); node.cleartextName = alternativeCleartext; node.extractedCiphertext = alternativeCiphertext; - eventConsumer.accept(new ConflictResolvedEvent(cleartextPath.resolve(cleartext), canonicalPath, cleartextPath.resolve(alternativeCleartext),alternativePath)); + eventConsumer.accept(new ConflictResolvedEvent(cleartextPath.resolve(cleartext), canonicalPath, cleartextPath.resolve(alternativeCleartext), alternativePath)); return node; } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index d83b65f7c..cfa688560 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Named; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; From 53d5cd94bbd0701b636d6ea545615bc451a43713 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 15:53:34 +0100 Subject: [PATCH 10/19] remove unused parameter in decryption failed event --- .../org/cryptomator/cryptofs/event/DecryptionFailedEvent.java | 3 +-- src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java | 2 +- .../java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java index 199197db9..d7b9eba7a 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/DecryptionFailedEvent.java @@ -7,10 +7,9 @@ /** * Emitted, if a decryption operation fails. * - * @param cleartextPath path within the cryptographic filesystem * @param ciphertextPath path to the encrypted resource * @param e thrown exception */ -public record DecryptionFailedEvent(Path cleartextPath, Path ciphertextPath, AuthenticationFailedException e) implements FilesystemEvent { +public record DecryptionFailedEvent(Path ciphertextPath, AuthenticationFailedException e) implements FilesystemEvent { } diff --git a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java index b8533887c..f138d1440 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/ChunkLoader.java @@ -53,7 +53,7 @@ public ByteBuffer load(Long chunkIndex) throws IOException, AuthenticationFailed } return cleartextBuf; } catch (AuthenticationFailedException e) { - eventConsumer.accept(new DecryptionFailedEvent(null, path.get(), e)); + eventConsumer.accept(new DecryptionFailedEvent(path.get(), e)); throw e; } finally { bufferPool.recycle(ciphertextBuf); diff --git a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java index cfa688560..0ca5ad5d6 100644 --- a/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java +++ b/src/main/java/org/cryptomator/cryptofs/fh/FileHeaderHolder.java @@ -82,7 +82,7 @@ FileHeader loadExisting(FileChannel ch) throws IOException { return existingHeader; } catch (IllegalArgumentException | CryptoException e) { if (e instanceof AuthenticationFailedException afe) { - eventConsumer.accept(new DecryptionFailedEvent(null, path.get(), afe)); + eventConsumer.accept(new DecryptionFailedEvent(path.get(), afe)); } throw new IOException("Unable to decrypt header of file " + path.get(), e); } From ea091f0e8e6d8b8985dedd38928c68977f8d048a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:07:08 +0100 Subject: [PATCH 11/19] ensure eventConsumer is never null --- .../org/cryptomator/cryptofs/CryptoFileSystemProperties.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index ec5a12e7c..451fde154 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -355,6 +355,9 @@ public Builder withMasterkeyFilename(String masterkeyFilename) { * @since 2.8.0 */ public Builder withFilesystemEventConsumer(Consumer eventConsumer) { + if (eventConsumer == null) { + throw new IllegalArgumentException("Parameter eventConsumer must not be null"); + } this.eventConsumer = eventConsumer; return this; } From 19117bcf2d41f18704de221d2ff37577fcc6a30c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:07:47 +0100 Subject: [PATCH 12/19] ensure, that on any RuntimeException during event consumption the filesystem stays valid --- .../cryptofs/CryptoFileSystemModule.java | 9 ++++- .../cryptofs/CryptoFileSystemProperties.java | 4 +++ .../cryptofs/CryptoFileSystemModuleTest.java | 35 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 376d300eb..101b68e58 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -41,6 +41,13 @@ public Optional provideNativeFileStore(@PathToVault Path pathToVault) @Provides @CryptoFileSystemScoped public Consumer provideFilesystemEventConsumer(CryptoFileSystemProperties fsProps) { - return (Consumer) fsProps.get(CryptoFileSystemProperties.PROPERTY_NOTIFY_METHOD); + var eventConsumer = fsProps.fsEventConsumner(); + return event -> { + try { + eventConsumer.accept(event); + } catch (RuntimeException e) { + LOG.warn("Filesystem event consumer failed with exception when processing event {}", event, e); + } + }; } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 451fde154..85dd8b52f 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -164,6 +164,10 @@ int shorteningThreshold() { return (int) get(PROPERTY_SHORTENING_THRESHOLD); } + Consumer fsEventConsumner() { + return (Consumer) get(PROPERTY_NOTIFY_METHOD); + } + @Override public Set> entrySet() { return entries; diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java new file mode 100644 index 000000000..fcd5cf828 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java @@ -0,0 +1,35 @@ +package org.cryptomator.cryptofs; + +import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent; +import org.cryptomator.cryptofs.event.FilesystemEvent; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.nio.file.Path; +import java.util.function.Consumer; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CryptoFileSystemModuleTest { + + CryptoFileSystemModule inTest = new CryptoFileSystemModule(); + + @Test + void testEventConsumerIsDecorated() { + var p = Mockito.mock(Path.class); + var event = new ConflictResolutionFailedEvent(p, p, new RuntimeException()); + var eventConsumer = (Consumer) mock(Consumer.class); + doThrow(new RuntimeException("fail")).when(eventConsumer).accept(event); + var props = mock(CryptoFileSystemProperties.class); + when(props.fsEventConsumner()).thenReturn(eventConsumer); + + var decoratedConsumer = inTest.provideFilesystemEventConsumer(props); + Assertions.assertDoesNotThrow(() -> decoratedConsumer.accept(event)); + verify(eventConsumer).accept(event); + } + +} From 2cd07fae1fc779bfa798ba287919ba9cb69227af Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:48:05 +0100 Subject: [PATCH 13/19] standardize parameter naming for conflict resolution events --- .../event/ConflictResolutionFailedEvent.java | 6 +++--- .../cryptofs/event/ConflictResolvedEvent.java | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java index 4a7af7ff2..c31dbd6c9 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolutionFailedEvent.java @@ -5,10 +5,10 @@ /** * Emitted, if the conflict resolution inside an encrypted directory failed * - * @param cleartextPath path within the cryptographic filesystem - * @param ciphertextPath path to the encrypted resource with the broken filename + * @param canonicalCleartextPath path of the canonical file within the cryptographic filesystem + * @param conflictingCiphertextPath path of the encrypted, conflicting file * @param reason exception, why the resolution failed */ -public record ConflictResolutionFailedEvent(Path cleartextPath, Path ciphertextPath, Exception reason) implements FilesystemEvent { +public record ConflictResolutionFailedEvent(Path canonicalCleartextPath, Path conflictingCiphertextPath, Exception reason) implements FilesystemEvent { } diff --git a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java index 26e54d54a..b01acbd15 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/ConflictResolvedEvent.java @@ -3,13 +3,18 @@ import java.nio.file.Path; /** - * Emitted, if a conflict inside an encrypted directory was resolved + * Emitted, if a conflict inside an encrypted directory was resolved. + *

+ * A conflict exists, if two encrypted files with the same base64url string exist, but the second file has an arbitrary suffix before the file extension. + * The file without the suffix is called canonical. + * The file with the suffix is called conflicting + * On successful conflict resolution the conflicting file is renamed to the resolved file * - * @param cleartextPath path within the cryptographic filesystem - * @param ciphertextPath path to the encrypted resource - * @param oldVersionCleartextPath path within the cryptographic filesystem of the renamed resource - * @param oldVersionCiphertextPath path to the renamed, encrypted resource + * @param canonicalCleartextPath path of the canonical file within the cryptographic filesystem + * @param conflictingCiphertextPath path of the encrypted, conflicting file + * @param resolvedCleartextPath path of the resolved file within the cryptographic filesystem + * @param resolvedCiphertextPath path of the resolved, encrypted file */ -public record ConflictResolvedEvent(Path cleartextPath, Path ciphertextPath, Path oldVersionCleartextPath, Path oldVersionCiphertextPath) implements FilesystemEvent { +public record ConflictResolvedEvent(Path canonicalCleartextPath, Path conflictingCiphertextPath, Path resolvedCleartextPath, Path resolvedCiphertextPath) implements FilesystemEvent { } From 60a0fc8f050947b2071f47f0968ed646d9fb9d60 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:54:04 +0100 Subject: [PATCH 14/19] fix wrong path resolution --- .../java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 0f86fd790..6d52f4072 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -69,7 +69,7 @@ public Stream process(Node node) { Path canonicalPath = node.ciphertextPath.resolveSibling(canonicalCiphertextFileName); return resolveConflict(node, canonicalPath); } catch (IOException e) { - eventConsumer.accept(new ConflictResolutionFailedEvent(cleartextPath.resolve(node.cleartextName), node.ciphertextPath.resolve(node.fullCiphertextFileName), e)); + eventConsumer.accept(new ConflictResolutionFailedEvent(cleartextPath.resolve(node.cleartextName), node.ciphertextPath, e)); LOG.error("Failed to resolve conflict for {}", node.ciphertextPath, e); return Stream.empty(); } From 30ee8825f4d56d26b25ce6648003d3c778d256ba Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:54:16 +0100 Subject: [PATCH 15/19] adjust doc --- .../java/org/cryptomator/cryptofs/event/FilesystemEvent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java index a2d7fb090..2a80d3b2e 100644 --- a/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java +++ b/src/main/java/org/cryptomator/cryptofs/event/FilesystemEvent.java @@ -11,8 +11,8 @@ * {@code * FilesystemEvent fse; * switch (fse) { - * case DecryptionFailedEvent dfe -> //do stuff - * case ConflictResolvedEvent cre -> //do other stuff + * case DecryptionFailedEvent e -> //do stuff + * case ConflictResolvedEvent e -> //do other stuff * //other cases * } * if( fse instanceof DecryptionFailedEvent dfe) { From 3691c6bd5a17901ffee920e83e3bae7e3ce70d06 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:56:26 +0100 Subject: [PATCH 16/19] rename eventConsumer variables in CryptoFileSystemProperties --- .../cryptofs/CryptoFileSystemProperties.java | 12 ++++++------ .../cryptofs/CryptoFileSystemPropertiesTest.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 85dd8b52f..59a3fec19 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -86,9 +86,9 @@ public class CryptoFileSystemProperties extends AbstractMap { * * @since 2.9.0 */ - public static final String PROPERTY_NOTIFY_METHOD = "notificationConsumer"; + public static final String PROPERTY_EVENT_CONSUMER = "notificationConsumer"; - static final Consumer DEFAULT_NOTIFY_METHOD = (FilesystemEvent e) -> {}; + static final Consumer DEFAULT_EVENT_CONSUMER = ignored -> {}; /** * Key identifying the filesystem flags. @@ -123,7 +123,7 @@ private CryptoFileSystemProperties(Builder builder) { Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), // Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), // Map.entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename), // - Map.entry(PROPERTY_NOTIFY_METHOD, builder.eventConsumer), // + Map.entry(PROPERTY_EVENT_CONSUMER, builder.eventConsumer), // Map.entry(PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, builder.maxCleartextNameLength), // Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), // Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo) // @@ -165,7 +165,7 @@ int shorteningThreshold() { } Consumer fsEventConsumner() { - return (Consumer) get(PROPERTY_NOTIFY_METHOD); + return (Consumer) get(PROPERTY_EVENT_CONSUMER); } @Override @@ -223,7 +223,7 @@ public static class Builder { private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME; private int maxCleartextNameLength = DEFAULT_MAX_CLEARTEXT_NAME_LENGTH; private int shorteningThreshold = DEFAULT_SHORTENING_THRESHOLD; - private Consumer eventConsumer = DEFAULT_NOTIFY_METHOD; + private Consumer eventConsumer = DEFAULT_EVENT_CONSUMER; private Builder() { } @@ -236,7 +236,7 @@ private Builder(Map properties) { checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength); checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold); checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo); - checkedSet(Consumer.class, PROPERTY_NOTIFY_METHOD, properties, this::withFilesystemEventConsumer); + checkedSet(Consumer.class, PROPERTY_EVENT_CONSUMER, properties, this::withFilesystemEventConsumer); } private void checkedSet(Class type, String key, Map properties, Consumer setter) { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 9690ead3f..87be1a283 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -52,7 +52,7 @@ public void testSetMasterkeyFilenameAndReadonlyFlag() { anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // - anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); + anEntry(PROPERTY_EVENT_CONSUMER, DEFAULT_EVENT_CONSUMER))); } @Test @@ -79,7 +79,7 @@ public void testFromMap() { anEntry(PROPERTY_SHORTENING_THRESHOLD, 221), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // - anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); + anEntry(PROPERTY_EVENT_CONSUMER, DEFAULT_EVENT_CONSUMER))); } @Test @@ -102,7 +102,7 @@ public void testWrapMapWithTrueReadonly() { anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.of(FileSystemFlags.READONLY)), // - anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); + anEntry(PROPERTY_EVENT_CONSUMER, DEFAULT_EVENT_CONSUMER))); } @Test @@ -125,7 +125,7 @@ public void testWrapMapWithFalseReadonly() { anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)), // - anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); + anEntry(PROPERTY_EVENT_CONSUMER, DEFAULT_EVENT_CONSUMER))); } @Test @@ -178,7 +178,7 @@ public void testWrapMapWithoutReadonly() { anEntry(PROPERTY_SHORTENING_THRESHOLD, DEFAULT_SHORTENING_THRESHOLD), // anEntry(PROPERTY_CIPHER_COMBO, DEFAULT_CIPHER_COMBO), // anEntry(PROPERTY_FILESYSTEM_FLAGS, EnumSet.noneOf(FileSystemFlags.class)), // - anEntry(PROPERTY_NOTIFY_METHOD, DEFAULT_NOTIFY_METHOD))); + anEntry(PROPERTY_EVENT_CONSUMER, DEFAULT_EVENT_CONSUMER))); } @Test From fb5b30207d11f921d2eae57c4102d3f81b2abbb7 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 6 Feb 2025 17:59:50 +0100 Subject: [PATCH 17/19] rename method --- .../java/org/cryptomator/cryptofs/CryptoFileSystemModule.java | 2 +- .../org/cryptomator/cryptofs/CryptoFileSystemProperties.java | 3 ++- .../org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java index 101b68e58..143f465f2 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java @@ -41,7 +41,7 @@ public Optional provideNativeFileStore(@PathToVault Path pathToVault) @Provides @CryptoFileSystemScoped public Consumer provideFilesystemEventConsumer(CryptoFileSystemProperties fsProps) { - var eventConsumer = fsProps.fsEventConsumner(); + var eventConsumer = fsProps.filesystemEventConsumer(); return event -> { try { eventConsumer.accept(event); diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 59a3fec19..728844caf 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -164,7 +164,8 @@ int shorteningThreshold() { return (int) get(PROPERTY_SHORTENING_THRESHOLD); } - Consumer fsEventConsumner() { + @SuppressWarnings("unchecked") + Consumer filesystemEventConsumer() { return (Consumer) get(PROPERTY_EVENT_CONSUMER); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java index fcd5cf828..4a0910742 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemModuleTest.java @@ -25,7 +25,7 @@ void testEventConsumerIsDecorated() { var eventConsumer = (Consumer) mock(Consumer.class); doThrow(new RuntimeException("fail")).when(eventConsumer).accept(event); var props = mock(CryptoFileSystemProperties.class); - when(props.fsEventConsumner()).thenReturn(eventConsumer); + when(props.filesystemEventConsumer()).thenReturn(eventConsumer); var decoratedConsumer = inTest.provideFilesystemEventConsumer(props); Assertions.assertDoesNotThrow(() -> decoratedConsumer.accept(event)); From 3df3c4e9873cf371d7700a04b9888afeeccc3c25 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 7 Feb 2025 17:15:41 +0100 Subject: [PATCH 18/19] increase test coverage --- .../cryptofs/CryptoFileSystemPropertiesTest.java | 16 ++++++++++++++++ .../cryptofs/fh/FileHeaderHolderTest.java | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java index 87be1a283..2b6890318 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemPropertiesTest.java @@ -161,6 +161,22 @@ public void testWrapMapWithInvalidPassphrase() { }); } + @Test + public void testWrapMapWithNullEventConsumer() { + Map map = new HashMap<>(); + map.put(PROPERTY_MASTERKEY_FILENAME, "any"); + map.put(PROPERTY_EVENT_CONSUMER, null); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + CryptoFileSystemProperties.wrap(map); + }); + } + + @Test + public void testNullEventConsumerThrowsIAE() { + Assertions.assertThrows(IllegalArgumentException.class, () -> CryptoFileSystemProperties.cryptoFileSystemProperties().withFilesystemEventConsumer(null)); + } + @Test public void testWrapMapWithoutReadonly() { Map map = new HashMap<>(); diff --git a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java index 98f7d6374..afc8ab318 100644 --- a/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/fh/FileHeaderHolderTest.java @@ -25,6 +25,7 @@ import java.util.function.Consumer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -89,8 +90,8 @@ public void testLoadExisting() throws IOException, AuthenticationFailedException } @Test - @DisplayName("load failure") - public void testLoadExistingFailure() { + @DisplayName("load failure due to authenticationFailedException") + public void testLoadExistingFailureWithAuthFailed() { Mockito.doThrow(AuthenticationFailedException.class).when(fileHeaderCryptor).decryptHeader(Mockito.any()); Assertions.assertThrows(IOException.class, () -> inTest.loadExisting(channel)); @@ -98,6 +99,16 @@ public void testLoadExistingFailure() { verify(eventConsumer).accept(ArgumentMatchers.argThat(isDecryptionFailedEvent)); } + @Test + @DisplayName("load failure due to IllegalArgumentException") + public void testLoadExistingFailureWithIllegalArgument() { + Mockito.doThrow(IllegalArgumentException.class).when(fileHeaderCryptor).decryptHeader(Mockito.any()); + + Assertions.assertThrows(IOException.class, () -> inTest.loadExisting(channel)); + var isDecryptionFailedEvent = (ArgumentMatcher) ev -> ev instanceof DecryptionFailedEvent; + verify(eventConsumer, never()).accept(ArgumentMatchers.argThat(isDecryptionFailedEvent)); + } + } @Nested From 9fddb8b13c9bc2ab87e8b66b413f6313f958c749 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 7 Feb 2025 17:19:47 +0100 Subject: [PATCH 19/19] align key name for event consumer entry in properties to public builder api Co-authored-by: Sebastian Stenzel --- .../org/cryptomator/cryptofs/CryptoFileSystemProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java index 728844caf..30bffeea0 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProperties.java @@ -86,7 +86,7 @@ public class CryptoFileSystemProperties extends AbstractMap { * * @since 2.9.0 */ - public static final String PROPERTY_EVENT_CONSUMER = "notificationConsumer"; + public static final String PROPERTY_EVENT_CONSUMER = "fsEventConsumer"; static final Consumer DEFAULT_EVENT_CONSUMER = ignored -> {};