mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 04:31:27 +00:00
New MAC authentication warning, preventing CCAs, but allowing to force-decrypt unauthentic files.
This commit is contained in:
@@ -20,7 +20,6 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
@@ -109,7 +108,7 @@ public class ChangePasswordController implements Initializable {
|
||||
try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
|
||||
vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
|
||||
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (DecryptFailedException | IOException ex) {
|
||||
} catch (IOException ex) {
|
||||
messageText.setText(rb.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
newPasswordField.swipe();
|
||||
|
||||
@@ -1,38 +1,83 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.WeakListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.cell.CheckBoxListCell;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
|
||||
public class MacWarningsController {
|
||||
public class MacWarningsController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private ListView<String> warningsList;
|
||||
private ListView<Warning> warningsList;
|
||||
|
||||
@FXML
|
||||
private Button whitelistButton;
|
||||
|
||||
private final Application application;
|
||||
private final ListChangeListener<? super String> macWarningsListener = this::warningsDidChange;
|
||||
private final ListChangeListener<? super String> weakMacWarningsListener = new WeakListChangeListener<>(macWarningsListener);
|
||||
private final ObservableList<Warning> warnings = FXCollections.observableArrayList();
|
||||
private final ListChangeListener<String> unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange;
|
||||
private final ChangeListener<Boolean> stageVisibilityChangeListener = this::windowVisibilityDidChange;
|
||||
private Stage stage;
|
||||
private Vault vault;
|
||||
private ResourceBundle rb;
|
||||
|
||||
@Inject
|
||||
public MacWarningsController(Application application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
warnings.addListener(this::warningsDidInvalidate);
|
||||
warningsList.setItems(warnings);
|
||||
warningsList.setCellFactory(CheckBoxListCell.forListView(Warning::selectedProperty, new StringConverter<Warning>() {
|
||||
|
||||
@Override
|
||||
public String toString(Warning object) {
|
||||
return object.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Warning fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickDismissButton(ActionEvent event) {
|
||||
warningsList.getItems().removeListener(weakMacWarningsListener);
|
||||
stage.hide();
|
||||
private void didClickWhitelistButton(ActionEvent event) {
|
||||
warnings.filtered(w -> w.isSelected()).stream().forEach(w -> {
|
||||
final String resourceToBeWhitelisted = w.getName();
|
||||
vault.getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted);
|
||||
vault.getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted);
|
||||
});
|
||||
warnings.removeIf(w -> w.isSelected());
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -40,26 +85,70 @@ public class MacWarningsController {
|
||||
application.getHostServices().showDocument("https://cryptomator.org/help.html#macWarning");
|
||||
}
|
||||
|
||||
// closes this window automatically, if all warnings disappeared (e.g. due to an unmount event)
|
||||
private void warningsDidChange(Change<? extends String> change) {
|
||||
if (change.getList().isEmpty() && stage != null) {
|
||||
change.getList().removeListener(weakMacWarningsListener);
|
||||
stage.hide();
|
||||
private void unauthenticatedResourcesDidChange(Change<? extends String> change) {
|
||||
while (change.next()) {
|
||||
if (change.wasAdded()) {
|
||||
warnings.addAll(change.getAddedSubList().stream().map(Warning::new).collect(Collectors.toList()));
|
||||
} else if (change.wasRemoved()) {
|
||||
change.getRemoved().forEach(str -> {
|
||||
warnings.removeIf(w -> str.equals(w.name.get()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
private void warningsDidInvalidate(Observable observable) {
|
||||
disableWhitelistButtonIfNothingSelected();
|
||||
}
|
||||
|
||||
private void windowVisibilityDidChange(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
stage.setTitle(String.format(rb.getString("macWarnings.windowTitle"), vault.getName()));
|
||||
warnings.addAll(vault.getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
|
||||
vault.getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
|
||||
} else {
|
||||
vault.getNamesOfResourcesWithInvalidMac().clear();
|
||||
vault.getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void disableWhitelistButtonIfNothingSelected() {
|
||||
whitelistButton.setDisable(warnings.filtered(w -> w.isSelected()).isEmpty());
|
||||
}
|
||||
|
||||
public void setStage(Stage stage) {
|
||||
this.stage = stage;
|
||||
stage.showingProperty().addListener(new WeakChangeListener<>(stageVisibilityChangeListener));
|
||||
}
|
||||
|
||||
public void setVault(Vault vault) {
|
||||
this.vault = vault;
|
||||
this.warningsList.setItems(vault.getNamesOfResourcesWithInvalidMac());
|
||||
this.warningsList.getItems().addListener(weakMacWarningsListener);
|
||||
}
|
||||
|
||||
private class Warning {
|
||||
|
||||
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(false);
|
||||
|
||||
public Warning(String name) {
|
||||
this.name.set(name);
|
||||
this.selectedProperty().addListener(change -> {
|
||||
disableWhitelistButtonIfNothingSelected();
|
||||
});
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public BooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
@Override
|
||||
public void didLock(UnlockedController ctrl) {
|
||||
showUnlockView(ctrl.getVault());
|
||||
if (getUnlockedDirectories().isEmpty()) {
|
||||
if (getUnlockedVaults().isEmpty()) {
|
||||
Platform.setImplicitExit(true);
|
||||
}
|
||||
}
|
||||
@@ -304,12 +304,12 @@ public class MainController implements Initializable, InitializationListener, Un
|
||||
|
||||
/* Convenience */
|
||||
|
||||
public Collection<Vault> getDirectories() {
|
||||
public Collection<Vault> getVaults() {
|
||||
return vaultList.getItems();
|
||||
}
|
||||
|
||||
public Collection<Vault> getUnlockedDirectories() {
|
||||
return getDirectories().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
|
||||
public Collection<Vault> getUnlockedVaults() {
|
||||
return getVaults().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/* public Getter/Setter */
|
||||
|
||||
@@ -35,7 +35,6 @@ import javafx.scene.text.Text;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
@@ -134,7 +133,7 @@ public class UnlockController implements Initializable {
|
||||
vault.setUnlocked(true);
|
||||
final Future<Boolean> futureMount = exec.submit(() -> vault.mount());
|
||||
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
|
||||
} catch (DecryptFailedException | IOException ex) {
|
||||
} catch (IOException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageText.setText(rb.getString("unlock.errorMessage.decryptionFailed"));
|
||||
@@ -178,6 +177,7 @@ public class UnlockController implements Initializable {
|
||||
setControlsDisabled(false);
|
||||
if (vault.isUnlocked() && !mountSuccess) {
|
||||
vault.stopServer();
|
||||
vault.setUnlocked(false);
|
||||
}
|
||||
if (mountSuccess && listener != null) {
|
||||
listener.didUnlock(this);
|
||||
|
||||
@@ -11,14 +11,12 @@ package org.cryptomator.ui.controllers;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.WeakListChangeListener;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -32,7 +30,6 @@ import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import org.cryptomator.crypto.CryptorIOSampling;
|
||||
@@ -48,9 +45,8 @@ public class UnlockedController implements Initializable {
|
||||
private static final int IO_SAMPLING_STEPS = 100;
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||
private final ControllerFactory controllerFactory;
|
||||
private final ListChangeListener<String> macWarningsListener = this::macWarningsDidChange;
|
||||
private final ListChangeListener<String> weakMacWarningsListener = new WeakListChangeListener<>(macWarningsListener);
|
||||
private final AtomicBoolean macWarningsWindowVisible = new AtomicBoolean();
|
||||
private final Stage macWarningWindow = new Stage();
|
||||
private MacWarningsController macWarningCtrl;
|
||||
private LockListener listener;
|
||||
private Vault vault;
|
||||
private Timeline ioAnimation;
|
||||
@@ -74,6 +70,22 @@ public class UnlockedController implements Initializable {
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
|
||||
try {
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/mac_warnings.fxml"), rb);
|
||||
loader.setControllerFactory(controllerFactory);
|
||||
|
||||
final Parent root = loader.load();
|
||||
macWarningWindow.setScene(new Scene(root));
|
||||
macWarningWindow.sizeToScene();
|
||||
macWarningWindow.setResizable(false);
|
||||
ActiveWindowStyleSupport.startObservingFocus(macWarningWindow);
|
||||
|
||||
macWarningCtrl = loader.getController();
|
||||
macWarningCtrl.setStage(macWarningWindow);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to load fxml file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -84,7 +96,6 @@ public class UnlockedController implements Initializable {
|
||||
messageLabel.setText(rb.getString("unlocked.label.unmountFailed"));
|
||||
return;
|
||||
}
|
||||
vault.getNamesOfResourcesWithInvalidMac().removeListener(weakMacWarningsListener);
|
||||
vault.stopServer();
|
||||
vault.setUnlocked(false);
|
||||
if (listener != null) {
|
||||
@@ -98,40 +109,16 @@ public class UnlockedController implements Initializable {
|
||||
|
||||
private void macWarningsDidChange(ListChangeListener.Change<? extends String> change) {
|
||||
if (change.getList().size() > 0) {
|
||||
Platform.runLater(this::showMacWarningsWindow);
|
||||
Platform.runLater(() -> {
|
||||
macWarningWindow.show();
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
macWarningWindow.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void showMacWarningsWindow() {
|
||||
if (macWarningsWindowVisible.getAndSet(true) == false) {
|
||||
try {
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/mac_warnings.fxml"), rb);
|
||||
loader.setControllerFactory(controllerFactory);
|
||||
|
||||
final Parent root = loader.load();
|
||||
final Stage stage = new Stage();
|
||||
stage.setTitle(String.format(rb.getString("macWarnings.windowTitle"), vault.getName()));
|
||||
stage.setScene(new Scene(root));
|
||||
stage.sizeToScene();
|
||||
stage.setResizable(false);
|
||||
stage.setOnHidden(this::onHideMacWarningsWindow);
|
||||
ActiveWindowStyleSupport.startObservingFocus(stage);
|
||||
|
||||
final MacWarningsController ctrl = loader.getController();
|
||||
ctrl.setVault(vault);
|
||||
ctrl.setStage(stage);
|
||||
|
||||
stage.show();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to load fxml file.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onHideMacWarningsWindow(WindowEvent event) {
|
||||
macWarningsWindowVisible.set(false);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// IO Graph
|
||||
// ****************************************
|
||||
@@ -194,8 +181,18 @@ public class UnlockedController implements Initializable {
|
||||
|
||||
public void setVault(Vault vault) {
|
||||
this.vault = vault;
|
||||
vault.getNamesOfResourcesWithInvalidMac().addListener(weakMacWarningsListener);
|
||||
macWarningCtrl.setVault(vault);
|
||||
|
||||
// listen to MAC warnings as long as this vault is unlocked:
|
||||
final ListChangeListener<String> macWarningsListener = this::macWarningsDidChange;
|
||||
vault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener);
|
||||
vault.unlockedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (Boolean.FALSE.equals(newValue)) {
|
||||
vault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener);
|
||||
}
|
||||
});
|
||||
|
||||
// sample crypto-throughput:
|
||||
if (vault.getCryptor() instanceof CryptorIOSampling) {
|
||||
startIoSampling((CryptorIOSampling) vault.getCryptor());
|
||||
} else {
|
||||
|
||||
@@ -105,7 +105,6 @@ public class Vault implements Serializable {
|
||||
} catch (DestroyFailedException e) {
|
||||
LOG.error("Destruction of cryptor throw an exception.", e);
|
||||
}
|
||||
setUnlocked(false);
|
||||
whitelistedResourcesWithInvalidMac.clear();
|
||||
namesOfResourcesWithInvalidMac.clear();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.cryptomator.ui.util;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.stage.Window;
|
||||
|
||||
public class ActiveWindowStyleSupport implements ChangeListener<Boolean> {
|
||||
@@ -18,9 +17,8 @@ public class ActiveWindowStyleSupport implements ChangeListener<Boolean> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and registers a listener on the given window, that will add the class {@value #ACTIVE_WINDOW_STYLE_CLASS} to the scenes root
|
||||
* element, if the window is active. Otherwise {@value #INACTIVE_WINDOW_STYLE_CLASS} will be added. Allows CSS rules to be defined
|
||||
* depending on the window's focus.<br/>
|
||||
* Creates and registers a listener on the given window, that will add the class {@value #ACTIVE_WINDOW_STYLE_CLASS} to the scenes root element, if the window is active. Otherwise
|
||||
* {@value #INACTIVE_WINDOW_STYLE_CLASS} will be added. Allows CSS rules to be defined depending on the window's focus.<br/>
|
||||
* <br/>
|
||||
* Example:<br/>
|
||||
* <code>
|
||||
@@ -32,7 +30,7 @@ public class ActiveWindowStyleSupport implements ChangeListener<Boolean> {
|
||||
* @return The observer
|
||||
*/
|
||||
public static ChangeListener<Boolean> startObservingFocus(final Window window) {
|
||||
final ChangeListener<Boolean> observer = new WeakChangeListener<Boolean>(new ActiveWindowStyleSupport(window));
|
||||
final ChangeListener<Boolean> observer = new ActiveWindowStyleSupport(window);
|
||||
window.focusedProperty().addListener(observer);
|
||||
return observer;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
class ObservableListOnMainThread<E> implements ObservableList<E> {
|
||||
|
||||
private final ObservableList<E> list;
|
||||
@@ -173,8 +175,9 @@ class ObservableListOnMainThread<E> implements ObservableList<E> {
|
||||
}
|
||||
|
||||
private void invalidated(Observable observable) {
|
||||
final Collection<InvalidationListener> listeners = ImmutableList.copyOf(invalidationListeners);
|
||||
Platform.runLater(() -> {
|
||||
for (InvalidationListener listener : invalidationListeners) {
|
||||
for (InvalidationListener listener : listeners) {
|
||||
listener.invalidated(this);
|
||||
}
|
||||
});
|
||||
@@ -192,8 +195,9 @@ class ObservableListOnMainThread<E> implements ObservableList<E> {
|
||||
|
||||
private void onChanged(Change<? extends E> change) {
|
||||
final Change<? extends E> c = new ListChange(change);
|
||||
final Collection<ListChangeListener<? super E>> listeners = ImmutableList.copyOf(listChangeListeners);
|
||||
Platform.runLater(() -> {
|
||||
for (ListChangeListener<? super E> listener : listChangeListeners) {
|
||||
for (ListChangeListener<? super E> listener : listeners) {
|
||||
listener.onChanged(c);
|
||||
}
|
||||
});
|
||||
@@ -206,7 +210,7 @@ class ObservableListOnMainThread<E> implements ObservableList<E> {
|
||||
|
||||
@Override
|
||||
public void removeListener(ListChangeListener<? super E> listener) {
|
||||
listChangeListeners.add(listener);
|
||||
listChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
private class ListChange extends ListChangeListener.Change<E> {
|
||||
|
||||
@@ -11,6 +11,8 @@ import javafx.collections.ObservableSet;
|
||||
import javafx.collections.SetChangeListener;
|
||||
import javafx.collections.SetChangeListener.Change;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
class ObservableSetOnMainThread<E> implements ObservableSet<E> {
|
||||
|
||||
private final ObservableSet<E> set;
|
||||
@@ -91,8 +93,9 @@ class ObservableSetOnMainThread<E> implements ObservableSet<E> {
|
||||
}
|
||||
|
||||
private void invalidated(Observable observable) {
|
||||
final Collection<InvalidationListener> listeners = ImmutableList.copyOf(invalidationListeners);
|
||||
Platform.runLater(() -> {
|
||||
for (InvalidationListener listener : invalidationListeners) {
|
||||
for (InvalidationListener listener : listeners) {
|
||||
listener.invalidated(this);
|
||||
}
|
||||
});
|
||||
@@ -110,8 +113,9 @@ class ObservableSetOnMainThread<E> implements ObservableSet<E> {
|
||||
|
||||
private void onChanged(Change<? extends E> change) {
|
||||
final Change<? extends E> c = new SetChange(this, change.getElementAdded(), change.getElementRemoved());
|
||||
final Collection<SetChangeListener<? super E>> listeners = ImmutableList.copyOf(setChangeListeners);
|
||||
Platform.runLater(() -> {
|
||||
for (SetChangeListener<? super E> listener : setChangeListeners) {
|
||||
for (SetChangeListener<? super E> listener : listeners) {
|
||||
listener.onChanged(c);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<ListView fx:id="warningsList" VBox.vgrow="ALWAYS" focusTraversable="false" />
|
||||
<HBox alignment="CENTER_RIGHT" spacing="12.0">
|
||||
<children>
|
||||
<Button text="%macWarnings.dismissButton" prefWidth="200.0" onAction="#didClickDismissButton" focusTraversable="false"/>
|
||||
<Button fx:id="whitelistButton" text="%macWarnings.whitelistButton" prefWidth="200.0" onAction="#didClickWhitelistButton" focusTraversable="false"/>
|
||||
<Button text="%macWarnings.moreInformationButton" defaultButton="true" prefWidth="200.0" onAction="#didClickMoreInformationButton" focusTraversable="false"/>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
@@ -59,7 +59,7 @@ unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
|
||||
macWarnings.windowTitle=Danger - Corrupted file in %s
|
||||
macWarnings.message=Cryptomator detected potentially malicious corruptions in the following files:
|
||||
macWarnings.moreInformationButton=Learn more
|
||||
macWarnings.dismissButton=I promise to be careful
|
||||
macWarnings.whitelistButton=Decrypt selected anyway
|
||||
|
||||
# tray icon
|
||||
tray.menu.open=Open
|
||||
|
||||
Reference in New Issue
Block a user