org.cryptomator.ui.UnlockController.java Source code

Java tutorial

Introduction

Here is the source code for org.cryptomator.ui.UnlockController.java

Source

/*******************************************************************************
 * 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.io.InputStream;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
import java.util.concurrent.Future;

import javafx.application.Platform;
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.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;

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.model.Directory;
import org.cryptomator.ui.util.FXThreads;
import org.cryptomator.ui.util.MasterKeyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UnlockController implements Initializable {

    private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);

    private ResourceBundle rb;
    private UnlockListener listener;
    private Directory directory;

    @FXML
    private ComboBox<String> usernameBox;

    @FXML
    private SecPasswordField passwordField;

    @FXML
    private CheckBox checkIntegrity;

    @FXML
    private Button unlockButton;

    @FXML
    private ProgressIndicator progressIndicator;

    @FXML
    private Label messageLabel;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        this.rb = rb;

        usernameBox.valueProperty().addListener(this::didChooseUsername);
    }

    // ****************************************
    // Username box
    // ****************************************

    public void didChooseUsername(ObservableValue<? extends String> property, String oldValue, String newValue) {
        if (newValue != null) {
            Platform.runLater(passwordField::requestFocus);
        }
        passwordField.setDisable(StringUtils.isEmpty(newValue));
    }

    // ****************************************
    // Unlock button
    // ****************************************

    @FXML
    private void didClickUnlockButton(ActionEvent event) {
        setControlsDisabled(true);
        final String masterKeyFileName = usernameBox.getValue() + Aes256Cryptor.MASTERKEY_FILE_EXT;
        final Path masterKeyPath = directory.getPath().resolve(masterKeyFileName);
        final CharSequence password = passwordField.getCharacters();
        InputStream masterKeyInputStream = null;
        try {
            progressIndicator.setVisible(true);
            masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
            directory.setVerifyFileIntegrity(checkIntegrity.isSelected());
            directory.getCryptor().decryptMasterKey(masterKeyInputStream, password);
            if (!directory.startServer()) {
                messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed"));
                directory.getCryptor().swipeSensitiveData();
                return;
            }
            directory.setUnlocked(true);
            final Future<Boolean> futureMount = FXThreads.runOnBackgroundThread(directory::mount);
            FXThreads.runOnMainThreadWhenFinished(futureMount, this::didUnlockAndMount);
            FXThreads.runOnMainThreadWhenFinished(futureMount, (result) -> {
                setControlsDisabled(false);
            });
        } catch (DecryptFailedException | IOException ex) {
            setControlsDisabled(false);
            progressIndicator.setVisible(false);
            messageLabel.setText(rb.getString("unlock.errorMessage.decryptionFailed"));
            LOG.error("Decryption failed for technical reasons.", ex);
        } catch (WrongPasswordException e) {
            setControlsDisabled(false);
            progressIndicator.setVisible(false);
            messageLabel.setText(rb.getString("unlock.errorMessage.wrongPassword"));
            Platform.runLater(passwordField::requestFocus);
        } catch (UnsupportedKeyLengthException ex) {
            setControlsDisabled(false);
            progressIndicator.setVisible(false);
            messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
            LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
        } finally {
            passwordField.swipe();
            IOUtils.closeQuietly(masterKeyInputStream);
        }
    }

    private void setControlsDisabled(boolean disable) {
        usernameBox.setDisable(disable);
        passwordField.setDisable(disable);
        checkIntegrity.setDisable(disable);
        unlockButton.setDisable(disable);
    }

    private void findExistingUsernames() {
        try {
            DirectoryStream<Path> ds = MasterKeyFilter.filteredDirectory(directory.getPath());
            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);
            }
            if (usernameBox.getItems().size() == 1) {
                usernameBox.getSelectionModel().selectFirst();
            }
        } catch (IOException e) {
            LOG.trace("Invalid path: " + directory.getPath(), e);
        }
    }

    private void didUnlockAndMount(boolean mountSuccess) {
        progressIndicator.setVisible(false);
        if (listener != null) {
            listener.didUnlock(this);
        }
    }

    /* Getter/Setter */

    public Directory getDirectory() {
        return directory;
    }

    public void setDirectory(Directory directory) {
        this.directory = directory;
        this.findExistingUsernames();
        this.checkIntegrity.setSelected(directory.shouldVerifyFileIntegrity());
    }

    public UnlockListener getListener() {
        return listener;
    }

    public void setListener(UnlockListener listener) {
        this.listener = listener;
    }

    /* callback */

    interface UnlockListener {
        void didUnlock(UnlockController ctrl);
    }

}