renamed to Cryptomator

This commit is contained in:
Sebastian Stenzel
2014-11-29 17:03:55 +01:00
parent 39d01c3106
commit 1876f82611
54 changed files with 125 additions and 349 deletions
@@ -0,0 +1,218 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.cryptomator.webdav.WebDAVServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AccessController implements Initializable {
private static final Logger LOG = LoggerFactory.getLogger(AccessController.class);
private final Aes256Cryptor cryptor = new Aes256Cryptor();
private ResourceBundle localization;
@FXML
private GridPane rootGridPane;
@FXML
private TextField workDirTextField;
@FXML
private ComboBox<String> usernameBox;
@FXML
private SecPasswordField passwordField;
@FXML
private Button startServerButton;
@FXML
private Label messageLabel;
@Override
public void initialize(URL url, ResourceBundle rb) {
this.localization = rb;
workDirTextField.textProperty().addListener(new WorkDirChangeListener());
usernameBox.valueProperty().addListener(new UsernameChangeListener());
workDirTextField.setText(Settings.load().getWebdavWorkDir());
usernameBox.setValue(Settings.load().getUsername());
}
/**
* Step 1: Choose encrypted storage:
*/
@FXML
protected void chooseWorkDir(ActionEvent event) {
messageLabel.setText(null);
final File currentFolder = new File(workDirTextField.getText());
final DirectoryChooser dirChooser = new DirectoryChooser();
if (currentFolder.exists()) {
dirChooser.setInitialDirectory(currentFolder);
}
final File file = dirChooser.showDialog(rootGridPane.getScene().getWindow());
if (file != null) {
workDirTextField.setText(file.toString());
}
}
private final class WorkDirChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (StringUtils.isEmpty(newValue)) {
usernameBox.setDisable(true);
usernameBox.setValue(null);
return;
}
boolean storageLocationValid;
try {
final Path storagePath = FileSystems.getDefault().getPath(workDirTextField.getText());
final DirectoryStream<Path> ds = MasterKeyFilter.filteredDirectory(storagePath);
final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
usernameBox.getItems().clear();
for (final Path path : ds) {
final String fileName = path.getFileName().toString();
final int beginOfExt = fileName.toLowerCase().lastIndexOf(masterKeyExt);
final String baseName = fileName.substring(0, beginOfExt);
usernameBox.getItems().add(baseName);
}
storageLocationValid = !usernameBox.getItems().isEmpty();
} catch (InvalidPathException | IOException ex) {
LOG.trace("Invalid path: " + workDirTextField.getText(), ex);
storageLocationValid = false;
}
// valid encrypted folder?
if (storageLocationValid) {
Settings.load().setWebdavWorkDir(workDirTextField.getText());
Settings.save();
} else {
messageLabel.setText(localization.getString("access.messageLabel.invalidStorageLocation"));
}
// enable/disable next controls:
usernameBox.setDisable(!storageLocationValid);
if (usernameBox.getItems().size() == 1) {
usernameBox.setValue(usernameBox.getItems().get(0));
}
}
}
/**
* Step 2: Choose username
*/
private final class UsernameChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (newValue != null) {
Settings.load().setUsername(newValue);
Settings.save();
}
passwordField.setDisable(StringUtils.isEmpty(newValue));
startServerButton.setDisable(StringUtils.isEmpty(newValue));
Platform.runLater(passwordField::requestFocus);
}
}
// step 3: Enter password
/**
* Step 4: Unlock storage
*/
@FXML
protected void startStopServer(ActionEvent event) {
messageLabel.setText(null);
if (WebDAVServer.getInstance().isRunning()) {
this.tryStop();
cryptor.swipeSensitiveData();
} else if (this.unlockStorage()) {
this.tryStart();
}
}
private boolean unlockStorage() {
final Path storagePath = FileSystems.getDefault().getPath(workDirTextField.getText());
final String masterKeyFileName = usernameBox.getValue() + Aes256Cryptor.MASTERKEY_FILE_EXT;
final Path masterKeyPath = storagePath.resolve(masterKeyFileName);
final CharSequence password = passwordField.getCharacters();
InputStream masterKeyInputStream = null;
try {
masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
cryptor.decryptMasterKey(masterKeyInputStream, password);
return true;
} catch (NoSuchFileException e) {
messageLabel.setText(localization.getString("access.messageLabel.invalidStorageLocation"));
LOG.warn("Invalid path: " + storagePath.toString());
} catch (DecryptFailedException ex) {
messageLabel.setText(localization.getString("access.messageLabel.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
} catch (WrongPasswordException e) {
messageLabel.setText(localization.getString("access.messageLabel.wrongPassword"));
} catch (UnsupportedKeyLengthException ex) {
messageLabel.setText(localization.getString("access.messageLabel.unsupportedKeyLengthInstallJCE"));
LOG.error("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
} catch (IOException ex) {
LOG.error("I/O Exception", ex);
} finally {
passwordField.swipe();
IOUtils.closeQuietly(masterKeyInputStream);
}
return false;
}
private void tryStart() {
try {
final Settings settings = Settings.load();
if (WebDAVServer.getInstance().start(settings.getWebdavWorkDir(), settings.getPort(), cryptor)) {
startServerButton.setText(localization.getString("access.button.stopServer"));
passwordField.setDisable(true);
}
} catch (NumberFormatException ex) {
LOG.error("Invalid port", ex);
}
}
private void tryStop() {
if (WebDAVServer.getInstance().stop()) {
startServerButton.setText(localization.getString("access.button.startServer"));
passwordField.setDisable(false);
}
}
}
@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import org.apache.commons.lang3.CharUtils;
import org.cryptomator.ui.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AdvancedController implements Initializable {
private static final Logger LOG = LoggerFactory.getLogger(AdvancedController.class);
@FXML
private GridPane rootGridPane;
@FXML
private TextField portTextField;
@Override
public void initialize(URL url, ResourceBundle rb) {
portTextField.setText(String.valueOf(Settings.load().getPort()));
portTextField.addEventFilter(KeyEvent.KEY_TYPED, new NumericKeyTypeEventFilter());
portTextField.focusedProperty().addListener(new PortTextFieldFocusListener());
}
/**
* Consumes key events, if typed key is not 0-9.
*/
private static final class NumericKeyTypeEventFilter implements EventHandler<KeyEvent> {
@Override
public void handle(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;
}
char c = t.getCharacter().charAt(0);
if (!CharUtils.isAsciiNumeric(c)) {
t.consume();
}
}
}
/**
* Saves port settings, when textfield loses focus.
*/
private class PortTextFieldFocusListener implements ChangeListener<Boolean> {
@Override
public void changed(ObservableValue<? extends Boolean> property, Boolean wasFocused, Boolean isFocused) {
final Settings settings = Settings.load();
try {
int port = Integer.valueOf(portTextField.getText());
settings.setPort(port);
} catch (NumberFormatException ex) {
LOG.warn("Invalid port " + portTextField.getText());
portTextField.setText(String.valueOf(settings.getPort()));
}
}
}
}
@@ -0,0 +1,200 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
import org.cryptomator.ui.controls.ClearOnDisableListener;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InitializeController implements Initializable {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
private static final int MAX_USERNAME_LENGTH = 200;
private ResourceBundle localization;
@FXML
private GridPane rootGridPane;
@FXML
private TextField workDirTextField;
@FXML
private TextField usernameField;
@FXML
private SecPasswordField passwordField;
@FXML
private SecPasswordField retypePasswordField;
@FXML
private Button initWorkDirButton;
@FXML
private Label messageLabel;
@Override
public void initialize(URL url, ResourceBundle rb) {
this.localization = rb;
workDirTextField.textProperty().addListener(new WorkDirChangeListener());
usernameField.addEventFilter(KeyEvent.KEY_TYPED, new AlphaNumericKeyTypeEventFilter());
usernameField.textProperty().addListener(new UsernameChangeListener());
usernameField.disableProperty().addListener(new ClearOnDisableListener(usernameField));
passwordField.textProperty().addListener(new PasswordChangeListener());
passwordField.disableProperty().addListener(new ClearOnDisableListener(passwordField));
retypePasswordField.textProperty().addListener(new RetypePasswordChangeListener());
retypePasswordField.disableProperty().addListener(new ClearOnDisableListener(retypePasswordField));
}
/**
* Step 1: Choose a directory, that shall be encrypted. On success, step 2 will be enabled.
*/
@FXML
protected void chooseWorkDir(ActionEvent event) {
final File currentFolder = new File(workDirTextField.getText());
final DirectoryChooser dirChooser = new DirectoryChooser();
if (currentFolder.exists()) {
dirChooser.setInitialDirectory(currentFolder);
}
final File file = dirChooser.showDialog(rootGridPane.getScene().getWindow());
if (file != null && file.canWrite()) {
workDirTextField.setText(file.toString());
}
}
private final class WorkDirChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (StringUtils.isEmpty(newValue)) {
usernameField.setDisable(true);
return;
}
try {
final Path dir = FileSystems.getDefault().getPath(newValue);
final boolean containsMasterKeys = MasterKeyFilter.filteredDirectory(dir).iterator().hasNext();
if (containsMasterKeys) {
usernameField.setDisable(true);
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
} else {
usernameField.setDisable(false);
messageLabel.setText(null);
}
} catch (InvalidPathException | IOException e) {
usernameField.setDisable(true);
messageLabel.setText(localization.getString("initialize.messageLabel.invalidPath"));
}
}
}
/**
* Step 2: Choose a valid username
*/
private static final class AlphaNumericKeyTypeEventFilter implements EventHandler<KeyEvent> {
@Override
public void handle(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;
}
char c = t.getCharacter().charAt(0);
if (!CharUtils.isAsciiAlphanumeric(c)) {
t.consume();
}
}
}
private final class UsernameChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
if (StringUtils.length(newValue) > MAX_USERNAME_LENGTH) {
usernameField.setText(newValue.substring(0, MAX_USERNAME_LENGTH));
}
passwordField.setDisable(StringUtils.isEmpty(usernameField.getText()));
}
}
/**
* Step 3: Defina a password. On success, step 3 will be enabled.
*/
private final class PasswordChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
retypePasswordField.setDisable(newValue.isEmpty());
}
}
/**
* Step 4: Retype the password. On success, step 4 will be enabled.
*/
private final class RetypePasswordChangeListener implements ChangeListener<String> {
@Override
public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
boolean passwordsAreEqual = passwordField.getText().equals(retypePasswordField.getText());
initWorkDirButton.setDisable(!passwordsAreEqual);
}
}
/**
* Step 5: Generate master password file in working directory. On success, print success message.
*/
@FXML
protected void initWorkDir(ActionEvent event) {
final Aes256Cryptor cryptor = new Aes256Cryptor();
final Path storagePath = FileSystems.getDefault().getPath(workDirTextField.getText());
final Path masterKeyPath = storagePath.resolve(usernameField.getText() + Aes256Cryptor.MASTERKEY_FILE_EXT);
final CharSequence password = passwordField.getCharacters();
OutputStream masterKeyOutputStream = null;
try {
masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
cryptor.encryptMasterKey(masterKeyOutputStream, password);
cryptor.swipeSensitiveData();
workDirTextField.clear();
} catch (FileAlreadyExistsException ex) {
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
} catch (InvalidPathException ex) {
messageLabel.setText(localization.getString("initialize.messageLabel.invalidPath"));
} catch (IOException ex) {
LOG.error("I/O Exception", ex);
} finally {
swipePasswordFields();
IOUtils.closeQuietly(masterKeyOutputStream);
}
}
private void swipePasswordFields() {
passwordField.swipe();
retypePasswordField.swipe();
}
}
@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui;
import java.io.IOException;
import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.webdav.WebDAVServer;
public class MainApplication extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(final Stage primaryStage) throws IOException {
final ResourceBundle localizations = ResourceBundle.getBundle("localization");
final Parent root = FXMLLoader.load(getClass().getResource("/main.fxml"), localizations);
final Scene scene = new Scene(root);
primaryStage.setTitle("Cryptomator");
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.setResizable(false);
primaryStage.show();
}
@Override
public void stop() throws Exception {
WebDAVServer.getInstance().stop();
Settings.save();
super.stop();
}
}
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class MainController {
@FXML
private VBox rootVBox;
@FXML
private Pane initializePanel;
@FXML
private Pane accessPanel;
@FXML
private Pane advancedPanel;
@FXML
protected void showInitializePane(ActionEvent event) {
showPanel(initializePanel);
}
@FXML
protected void showAccessPane(ActionEvent event) {
showPanel(accessPanel);
}
@FXML
protected void showAdvancedPane(ActionEvent event) {
showPanel(advancedPanel);
}
private void showPanel(Pane panel) {
rootVBox.getChildren().remove(1);
rootVBox.getChildren().add(panel);
rootVBox.getScene().getWindow().sizeToScene();
}
}
@@ -0,0 +1,22 @@
package org.cryptomator.ui.controls;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextInputControl;
public class ClearOnDisableListener implements ChangeListener<Boolean> {
final TextInputControl control;
public ClearOnDisableListener(TextInputControl control) {
this.control = control;
}
@Override
public void changed(ObservableValue<? extends Boolean> property, Boolean wasDisabled, Boolean isDisabled) {
if (isDisabled) {
control.clear();
}
}
}
@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.controls;
import java.util.Arrays;
import javafx.scene.control.PasswordField;
/**
* Compromise in security. While the text can be swiped, any access to the {@link #getText()} method will create a copy of the String in the heap.
*/
public class SecPasswordField extends PasswordField {
private static final char SWIPE_CHAR = ' ';
/**
* {@link #getContent()} uses a StringBuilder, which in turn is backed by a char[].
* The delete operation of AbstractStringBuilder closes the gap, that forms by deleting chars, by moving up the following chars.
* <br/>
* Imagine the following example with <code>pass</code> being the password, <code>x</code> being the swipe char and <code>'</code> being the offset of the char array:
* <ol>
* <li>Append filling chars to the end of the password: <code>passxxxx'</code></li>
* <li>Delete first 4 chars. Internal implementation will then copy the following chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
* <li>Delete first 4 chars again, as we appended 4 chars in step 1: <code>'xxxxxx</code></li>
* </ol>
*/
public void swipe() {
final int pwLength = this.getContent().length();
final char[] fillingChars = new char[pwLength];
Arrays.fill(fillingChars, SWIPE_CHAR);
this.getContent().insert(pwLength, new String(fillingChars), false);
this.getContent().delete(0, pwLength, true);
this.getContent().delete(0, pwLength, true);
}
}
@@ -0,0 +1,124 @@
/*******************************************************************************
* Copyright (c) 2014 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
package org.cryptomator.ui.settings;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.ObjectMapper;
@JsonPropertyOrder(value = {"webdavWorkDir"})
public class Settings implements Serializable {
private static final long serialVersionUID = 7609959894417878744L;
private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
private static final Path SETTINGS_DIR;
private static final String SETTINGS_FILE = "settings.json";
private static final ObjectMapper JSON_OM = new ObjectMapper();
private static Settings INSTANCE = null;
static {
final String home = System.getProperty("user.home", ".");
final String appdata = System.getenv("APPDATA");
final String os = System.getProperty("os.name").toLowerCase();
final FileSystem fs = FileSystems.getDefault();
if (os.contains("win") && appdata != null) {
SETTINGS_DIR = fs.getPath(appdata, "opencloudencryptor");
} else if (os.contains("win") && appdata == null) {
SETTINGS_DIR = fs.getPath(home, ".opencloudencryptor");
} else if (os.contains("mac")) {
SETTINGS_DIR = fs.getPath(home, "Library/Application Support/opencloudencryptor");
} else {
// (os.contains("solaris") || os.contains("sunos") || os.contains("linux") || os.contains("unix"))
SETTINGS_DIR = fs.getPath(home, ".opencloudencryptor");
}
}
private String webdavWorkDir;
private String username;
private int port;
private Settings() {
// private constructor
}
public static synchronized Settings load() {
if (INSTANCE == null) {
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final InputStream in = Files.newInputStream(settingsFile, StandardOpenOption.READ);
INSTANCE = JSON_OM.readValue(in, Settings.class);
return INSTANCE;
} catch (IOException e) {
LOG.warn("Failed to load settings, creating new one.");
INSTANCE = Settings.defaultSettings();
}
}
return INSTANCE;
}
public static synchronized void save() {
if (INSTANCE != null) {
try {
Files.createDirectories(SETTINGS_DIR);
final Path settingsFile = SETTINGS_DIR.resolve(SETTINGS_FILE);
final OutputStream out = Files.newOutputStream(settingsFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
JSON_OM.writeValue(out, INSTANCE);
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
}
}
}
private static Settings defaultSettings() {
final Settings result = new Settings();
result.setWebdavWorkDir(System.getProperty("user.home", "."));
return result;
}
/* Getter/Setter */
public String getWebdavWorkDir() {
return webdavWorkDir;
}
public void setWebdavWorkDir(String webdavWorkDir) {
this.webdavWorkDir = webdavWorkDir;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
@@ -0,0 +1,26 @@
package org.cryptomator.ui.util;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import org.cryptomator.crypto.aes256.Aes256Cryptor;
public class MasterKeyFilter implements Filter<Path> {
public static MasterKeyFilter FILTER = new MasterKeyFilter();
private final String masterKeyExt = Aes256Cryptor.MASTERKEY_FILE_EXT.toLowerCase();
@Override
public boolean accept(Path child) throws IOException {
return child.getFileName().toString().toLowerCase().endsWith(masterKeyExt);
}
public static final DirectoryStream<Path> filteredDirectory(Path dir) throws IOException {
return Files.newDirectoryStream(dir, FILTER);
}
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - initial API and implementation
-->
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import org.cryptomator.ui.controls.*?>
<GridPane fx:id="rootGridPane" fx:controller="org.cryptomator.ui.AccessController" xmlns:fx="http://javafx.com/fxml" styleClass="root" gridLinesVisible="false" vgap="5" hgap="5" prefWidth="480">
<stylesheets>
<URL value="@panels.css" />
</stylesheets>
<padding>
<Insets top="10" right="10" bottom="10" left="10" />
</padding>
<columnConstraints>
<ColumnConstraints minWidth="150" maxWidth="150" hgrow="NEVER" />
<ColumnConstraints minWidth="200" hgrow="ALWAYS" />
<ColumnConstraints minWidth="50" maxWidth="120" hgrow="NEVER" />
</columnConstraints>
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%access.label.workDir" GridPane.halignment="RIGHT" />
<TextField fx:id="workDirTextField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Button GridPane.rowIndex="0" GridPane.columnIndex="2" text="%access.button.chooseWorkDir" onAction="#chooseWorkDir" focusTraversable="false" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%access.label.username" GridPane.halignment="RIGHT" />
<ComboBox fx:id="usernameBox" GridPane.rowIndex="1" GridPane.columnIndex="1" promptText="$access.label.username" disable="true" />
<!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%access.label.password" GridPane.halignment="RIGHT" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="2" GridPane.columnIndex="1" disable="true" />
<!-- Row 3 -->
<Button fx:id="startServerButton" text="%access.button.startServer" GridPane.rowIndex="3" GridPane.columnIndex="1" disable="true" defaultButton="true" onAction="#startStopServer" focusTraversable="false" />
<!-- Row 4 -->
<Label fx:id="messageLabel" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="3" textAlignment="CENTER" />
</children>
</GridPane>
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - initial API and implementation
-->
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane fx:id="rootGridPane" fx:controller="org.cryptomator.ui.AdvancedController" xmlns:fx="http://javafx.com/fxml" styleClass="root" gridLinesVisible="false" vgap="5" hgap="5" prefWidth="480">
<stylesheets>
<URL value="@panels.css" />
</stylesheets>
<padding>
<Insets top="10" right="10" bottom="10" left="10" />
</padding>
<columnConstraints>
<ColumnConstraints minWidth="150" maxWidth="150" hgrow="NEVER" />
<ColumnConstraints minWidth="250" hgrow="ALWAYS" />
</columnConstraints>
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%advanced.label.port" GridPane.halignment="RIGHT" />
<TextField fx:id="portTextField" GridPane.rowIndex="0" GridPane.columnIndex="1" />
</children>
</GridPane>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - initial API and implementation
-->
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import org.cryptomator.ui.controls.*?>
<GridPane fx:id="rootGridPane" fx:controller="org.cryptomator.ui.InitializeController" xmlns:fx="http://javafx.com/fxml" styleClass="root" gridLinesVisible="false" vgap="5" hgap="5" prefWidth="480">
<stylesheets>
<URL value="@panels.css" />
</stylesheets>
<padding>
<Insets top="10" right="10" bottom="10" left="10" />
</padding>
<columnConstraints>
<ColumnConstraints minWidth="150" maxWidth="150" hgrow="NEVER" />
<ColumnConstraints minWidth="200" hgrow="ALWAYS" />
<ColumnConstraints minWidth="50" maxWidth="120" hgrow="NEVER" />
</columnConstraints>
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.workDir" GridPane.halignment="RIGHT" />
<TextField fx:id="workDirTextField" GridPane.rowIndex="0" GridPane.columnIndex="1" editable="true" />
<Button fx:id="chooseWorkDirButton" GridPane.rowIndex="0" GridPane.columnIndex="2" text="%initialize.button.chooseWorkDir" onAction="#chooseWorkDir" focusTraversable="false" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.username" GridPane.halignment="RIGHT" />
<TextField fx:id="usernameField" GridPane.rowIndex="1" GridPane.columnIndex="1" disable="true" />
<!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%initialize.label.password" GridPane.halignment="RIGHT" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="2" GridPane.columnIndex="1" disable="true" />
<!-- Row 3 -->
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%initialize.label.retypePassword" GridPane.halignment="RIGHT" />
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="3" GridPane.columnIndex="1" disable="true" />
<!-- Row 4 -->
<Button fx:id="initWorkDirButton" text="%initialize.button.initWorkDir" GridPane.rowIndex="4" GridPane.columnIndex="1" defaultButton="true" onAction="#initWorkDir" disable="true" focusTraversable="false"/>
<!-- Row 5 -->
<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="5" textOverrun="ELLIPSIS" />
</children>
</GridPane>
@@ -0,0 +1,37 @@
#-------------------------------------------------------------------------------
# Copyright (c) 2014 Sebastian Stenzel
# This file is licensed under the terms of the MIT license.
# See the LICENSE.txt file for more info.
#
# Contributors:
# Sebastian Stenzel - initial API and implementation
#-------------------------------------------------------------------------------
# main.fxml
toolbarbutton.initialize=Initialize Vault
toolbarbutton.access=Access Vault
toolbarbutton.advanced=Advanced Settings
# initialize.fxml
initialize.label.workDir=New vault location
initialize.button.chooseWorkDir=Choose...
initialize.label.username=Username
initialize.label.password=Password
initialize.label.retypePassword=Retype
initialize.button.initWorkDir=Initialize Vault
initialize.messageLabel.alreadyInitialized=Vault in this location already exists.
initialize.messageLabel.invalidPath=Invalid vault location.
# access.fxml
access.label.workDir=Vault location
access.label.username=Username
access.label.password=Password
access.button.chooseWorkDir=Choose...
access.button.startServer=Start Server
access.button.stopServer=Stop Server
access.messageLabel.wrongPassword=Wrong password.
access.messageLabel.invalidStorageLocation=Vault directory invalid.
access.messageLabel.decryptionFailed=Decryption failed.
access.messageLabel.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE.
# advanced.fxml
advanced.label.port=WebDAV Port
+32
View File
@@ -0,0 +1,32 @@
@CHARSET "US-ASCII";
.text {
-fx-font-smoothing-type: lcd;
}
.tool-bar {
-fx-background-color: linear-gradient(to bottom, #888888, #222222);
-fx-padding: 5.0 10.0 5.0 10.0;
-fx-border-color: #888888;
-fx-border-width: 1.0 0.0 1.0 0.0;
-fx-border-insets: 0.0;
-fx-alignment: CENTER;
}
.tool-bar .toggle-button {
-fx-text-fill: #FFFFFF;
-fx-background-color: linear-gradient(to bottom, #888888, #222222);
-fx-border-color: #888888;
-fx-background-insets: 0.0, 1.0;
-fx-background-radius: 4.0, 4.0;
-fx-border-radius: 3.0;
-fx-border-width: 0.5;
-fx-font-family: "lucida-grande";
-fx-font-weight: bold;
}
.tool-bar .toggle-button:armed,
.tool-bar .toggle-button:selected {
-fx-background-color: linear-gradient(to bottom, #444444, #555555 30%, #000000);
-fx-border-color: #FFFFFF;
}
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014 Sebastian Stenzel
This file is licensed under the terms of the MIT license.
See the LICENSE.txt file for more info.
Contributors:
Sebastian Stenzel - initial API and implementation
-->
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<VBox fx:id="rootVBox" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
<stylesheets>
<URL value="@main.css" />
</stylesheets>
<fx:define>
<fx:include fx:id="initializePanel" source="initialize.fxml" />
<fx:include fx:id="accessPanel" source="access.fxml" />
<fx:include fx:id="advancedPanel" source="advanced.fxml" />
</fx:define>
<children>
<ToolBar>
<items>
<fx:define>
<ToggleGroup fx:id="toolbarButtonGroup" />
</fx:define>
<ToggleButton text="%toolbarbutton.initialize" toggleGroup="$toolbarButtonGroup" onAction="#showInitializePane" />
<ToggleButton text="%toolbarbutton.access" toggleGroup="$toolbarButtonGroup" onAction="#showAccessPane" selected="true" />
<ToggleButton text="%toolbarbutton.advanced" toggleGroup="$toolbarButtonGroup" onAction="#showAdvancedPane" />
</items>
</ToolBar>
<fx:reference source="accessPanel"/>
</children>
</VBox>
+62
View File
@@ -0,0 +1,62 @@
@CHARSET "US-ASCII";
.root {
-fx-background-color: linear-gradient(to bottom, #FFFFFF, #DDDDDD);
}
.text {
-fx-font-smoothing-type: lcd;
}
.label {
-fx-alignment: CENTER;
-fx-font-family: "lucida-grande";
}
.button,
.combo-box {
-fx-text-fill: #000000;
-fx-background-color: linear-gradient(to bottom, #FFFFFF, #DDDDDD);
-fx-border-color: #888888;
-fx-background-insets: 0.0, 1.0;
-fx-background-radius: 4.0, 4.0;
-fx-border-radius: 3.0;
-fx-border-width: 0.5;
-fx-font-family: "lucida-grande";
-fx-font-weight: normal;
}
.text-field {
-fx-border-radius: 3.0;
-fx-border-width: 0.5;
-fx-border-color: #888888;
-fx-focus-color: #FF0000;
-fx-background-color: transparent;
-fx-padding: 5 2 5 2;
}
.text-field:focused {
-fx-background-color: #DDDDDD;
}
.button:armed,
.button:selected,
.combo-box:armed,
.combo-box:selected {
-fx-background-color: linear-gradient(to bottom, #DDDDDD, #CCCCCC 30%, #EEEEEE);
}
.combo-box .list-cell {
-fx-background-color: transparent;
-fx-text-fill: -fx-text-base-color;
}
.combo-box .list-cell:hover {
-fx-background-color: #DDDDDD;
}
.combo-box-popup .list-view {
-fx-padding: 0 0 0 0;
-fx-background-insets: 0, 0;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.6), 8, 0.0, 0, 0);
}