show badge for missing license key in window title

This commit is contained in:
Sebastian Stenzel
2019-11-20 17:11:54 +01:00
parent cceafb76ed
commit 9b019726bb
14 changed files with 199 additions and 68 deletions

View File

@@ -19,7 +19,7 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Optional;
@Singleton
public class LicenseChecker {
class LicenseChecker {
private final JWTVerifier verifier;

View File

@@ -0,0 +1,79 @@
package org.cryptomator.common;
import com.auth0.jwt.interfaces.DecodedJWT;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.cryptomator.common.settings.Settings;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
public class LicenseHolder {
private final Settings settings;
private final LicenseChecker licenseChecker;
private final ObjectProperty<DecodedJWT> validJwtClaims;
private final StringBinding licenseSubject;
private final BooleanBinding validLicenseProperty;
@Inject
public LicenseHolder(LicenseChecker licenseChecker, Settings settings) {
this.settings = settings;
this.licenseChecker = licenseChecker;
this.validJwtClaims = new SimpleObjectProperty<>();
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
this.validLicenseProperty = validJwtClaims.isNotNull();
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
validJwtClaims.set(claims.orElse(null));
}
public boolean validateAndStoreLicense(String licenseKey) {
Optional<DecodedJWT> claims = licenseChecker.check(licenseKey);
validJwtClaims.set(claims.orElse(null));
if (claims.isPresent()) {
settings.licenseKey().set(licenseKey);
return true;
} else {
return false;
}
}
/* Observable Properties */
public Optional<String> getLicenseKey() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return Optional.of(claims.getToken());
} else {
return Optional.empty();
}
}
public StringBinding licenseSubjectProperty() {
return licenseSubject;
}
public String getLicenseSubject() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return claims.getSubject();
} else {
return null;
}
}
public BooleanBinding validLicenseProperty() {
return validLicenseProperty;
}
public boolean isValidLicense() {
return validLicenseProperty.get();
}
}

View File

@@ -10,6 +10,7 @@ public enum FontAwesome5Icon {
COG("\uF013"), //
COGS("\uF085"), //
EXCLAMATION("\uF12A"),
EXCLAMATION_CIRCLE("\uF06A"), //
EXCLAMATION_TRIANGLE("\uF071"), //
EYE("\uF06E"), //
EYE_SLASH("\uF070"), //

View File

@@ -18,6 +18,7 @@ import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.slf4j.Logger;
@@ -79,9 +80,9 @@ public class FxApplication extends Application {
}
}
public void showPreferencesWindow() {
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
Platform.runLater(() -> {
Stage stage = preferencesWindow.get().showPreferencesWindow();
Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
addVisibleStage(stage);
LOG.debug("Showing Preferences");
});

View File

@@ -10,10 +10,12 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +40,7 @@ public class MainWindowController implements FxController {
private final boolean minimizeToSysTray;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
private final VaultListManager vaultListManager;
private final WrongFileAlertComponent.Builder wrongFileAlert;
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
@@ -49,12 +52,13 @@ public class MainWindowController implements FxController {
private double yOffset;
@Inject
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
this.licenseHolder = licenseHolder;
this.vaultListManager = vaultListManager;
this.wrongFileAlert = wrongFileAlert;
}
@@ -136,11 +140,20 @@ public class MainWindowController implements FxController {
@FXML
public void showPreferences() {
application.showPreferencesWindow();
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showDonationKeyPreferences() {
application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
}
/* Getter/Setter */
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}

View File

@@ -1,60 +1,37 @@
package org.cryptomator.ui.preferences;
import com.auth0.jwt.interfaces.DecodedJWT;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import org.cryptomator.common.LicenseChecker;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import java.util.Optional;
@PreferencesScoped
public class LicenseKeyPreferencesController implements FxController {
private static final String DONATION_URI = "https://cryptomator.org/#donate";
private final Settings settings;
private final Application application;
private final LicenseChecker licenseChecker;
private final ObjectProperty<DecodedJWT> validJwtClaims;
private final StringBinding licenseSubject;
private final BooleanBinding validLicenseProperty;
private final LicenseHolder licenseHolder;
public TextArea donationKeyField;
@Inject
LicenseKeyPreferencesController(Settings settings, Application application, LicenseChecker licenseChecker) {
this.settings = settings;
LicenseKeyPreferencesController(Application application, LicenseHolder licenseHolder) {
this.application = application;
this.licenseChecker = licenseChecker;
this.validJwtClaims = new SimpleObjectProperty<>();
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
this.validLicenseProperty = validJwtClaims.isNotNull();
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
validJwtClaims.set(claims.orElse(null));
this.licenseHolder = licenseHolder;
}
@FXML
public void initialize() {
donationKeyField.setText(settings.licenseKey().get());
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
}
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
Optional<DecodedJWT> claims = licenseChecker.check(newValue);
validJwtClaims.set(claims.orElse(null));
if (claims.isPresent()) {
settings.licenseKey().set(newValue);
}
licenseHolder.validateAndStoreLicense(newValue);
}
@FXML
@@ -62,26 +39,7 @@ public class LicenseKeyPreferencesController implements FxController {
application.getHostServices().showDocument(DONATION_URI);
}
/* Observable Properties */
public StringBinding licenseSubjectProperty() {
return licenseSubject;
}
public String getLicenseSubject() {
DecodedJWT claims = validJwtClaims.get();
if (claims != null) {
return claims.getSubject();
} else {
return null;
}
}
public BooleanBinding validLicenseProperty() {
return validLicenseProperty;
}
public boolean isValidLicense() {
return validLicenseProperty.get();
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
}

View File

@@ -7,9 +7,9 @@ package org.cryptomator.ui.preferences;
import dagger.Lazy;
import dagger.Subcomponent;
import javafx.beans.property.ObjectProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.cryptomator.ui.common.FXMLLoaderFactory;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
@FxmlScene(FxmlFile.PREFERENCES)
Lazy<Scene> scene();
default Stage showPreferencesWindow() {
ObjectProperty<SelectedPreferencesTab> selectedTabProperty();
default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
selectedTabProperty().set(selectedTab);
Stage stage = window();
stage.setScene(scene().get());
stage.show();

View File

@@ -1,6 +1,9 @@
package org.cryptomator.ui.preferences;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
@@ -15,25 +18,50 @@ import javax.inject.Inject;
public class PreferencesController implements FxController {
private final Stage window;
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
private final BooleanBinding updateAvailable;
public TabPane tabPane;
public Tab generalTab;
public Tab volumeTab;
public Tab updatesTab;
public Tab donationKeyTab;
@Inject
public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) {
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
this.window = window;
this.selectedTabProperty = selectedTabProperty;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
}
@FXML
public void initialize() {
window.setOnShowing(this::windowWillAppear);
selectedTabProperty.addListener(observable -> this.selectChosenTab());
}
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
if (updateAvailable.get()) {
tabPane.getSelectionModel().select(updatesTab);
private void selectChosenTab() {
Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
tabPane.getSelectionModel().select(toBeSelected);
}
private Tab getTabToSelect(SelectedPreferencesTab selectedTab) {
switch (selectedTab) {
case UPDATES:
return updatesTab;
case VOLUME:
return volumeTab;
case DONATION_KEY:
return donationKeyTab;
case GENERAL:
return generalTab;
case ANY:
default:
return updateAvailable.get() ? updatesTab : generalTab;
}
}
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
selectChosenTab();
}
}

View File

@@ -4,6 +4,8 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
@@ -23,6 +25,12 @@ import java.util.ResourceBundle;
@Module
abstract class PreferencesModule {
@Provides
@PreferencesScoped
static ObjectProperty<SelectedPreferencesTab> provideSelectedTabProperty() {
return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
}
@Provides
@PreferencesWindow
@PreferencesScoped

View File

@@ -0,0 +1,28 @@
package org.cryptomator.ui.preferences;
public enum SelectedPreferencesTab {
/**
* Let the controller decide which tab to show.
*/
ANY,
/**
* Show general tab
*/
GENERAL,
/**
* Show volume tab
*/
VOLUME,
/**
* Show updates tab
*/
UPDATES,
/**
* Show donation key tab
*/
DONATION_KEY,
}

View File

@@ -8,6 +8,7 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.launcher.FxApplicationStarter;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -150,7 +151,7 @@ class TrayMenuController {
}
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
fxApplicationStarter.get(true).thenAccept(FxApplication::showPreferencesWindow);
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
}
private void handleQuitRequest(EventObject e, QuitResponse response) {

View File

@@ -22,10 +22,21 @@
<children>
<Label text="Cryptomator"/>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.donationKeyMissing.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS"/>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
@@ -35,7 +46,7 @@
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES"/>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>

View File

@@ -19,15 +19,15 @@
</padding>
<children>
<StackPane VBox.vgrow="NEVER" prefHeight="60">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.validLicense}">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="USER_CROWN" glyphSize="24"/>
</StackPane>
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseSubject}" wrapText="true"/>
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.validLicense}">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>

View File

@@ -132,6 +132,7 @@ preferences.donationKey.getDonationKey=Get a donation key
# Main Window
main.closeBtn.tooltip=Close
main.preferencesBtn.tooltip=Preferences
main.donationKeyMissing.tooltip=Please consider donating
## Drag 'n' Drop
main.dropZone.dropVault=Add this vault
main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
@@ -196,4 +197,3 @@ passwordStrength.messageLabel.4=Very strong
# Quit
quit.prompt=Quit application? There are unlocked vaults.
quit.lockAndQuit=Lock and Quit