mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
225
Commits
1.5.12
...
1.6.0-alpha1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e5035f506 | ||
|
|
0ce41e7ac3 | ||
|
|
c1a249fd7f | ||
|
|
c266c7583b | ||
|
|
00331d4857 | ||
|
|
8fd484e2bb | ||
|
|
165d740acd | ||
|
|
fd4010c6c9 | ||
|
|
aa2900fa9e | ||
|
|
76d1875e01 | ||
|
|
bc83e23a34 | ||
|
|
6a326bcbce | ||
|
|
573ad03d68 | ||
|
|
d710d406d0 | ||
|
|
554f245f5d | ||
|
|
34995088ba | ||
|
|
69c63702f2 | ||
|
|
db7e731554 | ||
|
|
e08b43bf17 | ||
|
|
58b45bacd1 | ||
|
|
4f942bc23f | ||
|
|
09a8618fa0 | ||
|
|
52af530d1b | ||
|
|
86a42234c6 | ||
|
|
37c4e78b1d | ||
|
|
9c2c234bee | ||
|
|
59f91267ae | ||
|
|
b3ff9423b4 | ||
|
|
e3f3090341 | ||
|
|
83be6c0864 | ||
|
|
c654951dc1 | ||
|
|
4e177c9ea7 | ||
|
|
dc3a951a1b | ||
|
|
defa9c75eb | ||
|
|
c1f498a114 | ||
|
|
8457c50ebc | ||
|
|
0144cbb99f | ||
|
|
d6e4c7d177 | ||
|
|
3376b16b7b | ||
|
|
673fdcd095 | ||
|
|
0d00520ac1 | ||
|
|
22a0d3a9a5 | ||
|
|
24baa44e70 | ||
|
|
03886f88e8 | ||
|
|
642816b631 | ||
|
|
cd5c55aad7 | ||
|
|
b066b4b045 | ||
|
|
41d2a2c77e | ||
|
|
0840695e0a | ||
|
|
8447f105b0 | ||
|
|
c306d8df04 | ||
|
|
beba6490c3 | ||
|
|
fb1078b35b | ||
|
|
be0912e6ca | ||
|
|
2810c044ea | ||
|
|
e09bd160b7 | ||
|
|
e6d1e4697c | ||
|
|
cf7cbae567 | ||
|
|
803f517c62 | ||
|
|
0afa7b8e37 | ||
|
|
1a0f70f8e8 | ||
|
|
8737eb83f0 | ||
|
|
a96239a19f | ||
|
|
2b7cfcd1dc | ||
|
|
ebccb61750 | ||
|
|
285f2aec23 | ||
|
|
217e31fbd7 | ||
|
|
f0ebf7a638 | ||
|
|
c05c5e3f90 | ||
|
|
30f0c5e697 | ||
|
|
f989b8627c | ||
|
|
ff84230566 | ||
|
|
3b4f6276b5 | ||
|
|
2be7a050a4 | ||
|
|
96a612127c | ||
|
|
0bdfb7c9f9 | ||
|
|
f556301249 | ||
|
|
f77b237e59 | ||
|
|
61b7a39aad | ||
|
|
16bd84ee62 | ||
|
|
3d0a97fcdf | ||
|
|
786d156b9f | ||
|
|
629b6fb97d | ||
|
|
17dc32bb79 | ||
|
|
c05e00d32a | ||
|
|
9dc8b2cb47 | ||
|
|
0e32e96c7d | ||
|
|
97afadd7b9 | ||
|
|
b199b65e38 | ||
|
|
999abf3c13 | ||
|
|
2cdde54db6 | ||
|
|
6941603cdd | ||
|
|
cdca4e047d | ||
|
|
31e938de6a | ||
|
|
4c10ab764a | ||
|
|
4e075ab0ca | ||
|
|
f73ae9759f | ||
|
|
f6283b2f7e | ||
|
|
b23d2e4def | ||
|
|
91a2943599 | ||
|
|
e32ce22d24 | ||
|
|
e75c415b46 | ||
|
|
0ab28602d1 | ||
|
|
62c8edff04 | ||
|
|
d01c6268f8 | ||
|
|
e083fd0bcc | ||
|
|
ff687f1bae | ||
|
|
557aaa2480 | ||
|
|
78e43d401d | ||
|
|
7b08c5d287 | ||
|
|
84d2644f19 | ||
|
|
f9a9d7b870 | ||
|
|
d75a8c4bba | ||
|
|
d326c7c990 | ||
|
|
e8e63ca4fc | ||
|
|
6878f2e94b | ||
|
|
a17b416262 | ||
|
|
fed572694f | ||
|
|
fe3d67d937 | ||
|
|
3dcdeb1033 | ||
|
|
0898158c5a | ||
|
|
81091d4bdf | ||
|
|
c9b1b1baa5 | ||
|
|
c81f3bd972 | ||
|
|
9f86b74320 | ||
|
|
2d8390a0f3 | ||
|
|
8d3671175f | ||
|
|
366011a222 | ||
|
|
c0c2e21055 | ||
|
|
63be96a151 | ||
|
|
fc1df4bd99 | ||
|
|
7657330931 | ||
|
|
4e44de3c3b | ||
|
|
8d902a72ca | ||
|
|
7378031a72 | ||
|
|
f898c07c27 | ||
|
|
c8f02d1045 | ||
|
|
4cc13b986b | ||
|
|
af7b6780f3 | ||
|
|
4a17d01004 | ||
|
|
1d68408e21 | ||
|
|
821b1ee660 | ||
|
|
c6616bd467 | ||
|
|
4bb2589f16 | ||
|
|
896c3b3565 | ||
|
|
6ee717a693 | ||
|
|
07ec858e1e | ||
|
|
a7c1e43ebc | ||
|
|
ac1cd3b073 | ||
|
|
7474519faf | ||
|
|
340e474c33 | ||
|
|
c90ab75427 | ||
|
|
848b3f77d3 | ||
|
|
fb8cdb31ca | ||
|
|
ba5f8053fb | ||
|
|
38165becca | ||
|
|
f731ac5c90 | ||
|
|
0291702736 | ||
|
|
ab63929861 | ||
|
|
ca36fb8af3 | ||
|
|
8cb29ff83b | ||
|
|
304fa67dd7 | ||
|
|
a80ec32d2c | ||
|
|
dc4f21162e | ||
|
|
3b8d7e0241 | ||
|
|
6891c68cf1 | ||
|
|
18b26fbc58 | ||
|
|
1db8da6b6b | ||
|
|
14bce7e91d | ||
|
|
2674bc6f23 | ||
|
|
74e1d6f991 | ||
|
|
25dba028f8 | ||
|
|
bf51cc13d1 | ||
|
|
24be36cccb | ||
|
|
797e15c775 | ||
|
|
2395dbcae9 | ||
|
|
efacbbe2b6 | ||
|
|
2518086e46 | ||
|
|
c733247611 | ||
|
|
4f44e71e13 | ||
|
|
02aa46ece8 | ||
|
|
635f64c847 | ||
|
|
0a8516ae9a | ||
|
|
c1edb30472 | ||
|
|
93c78b63ca | ||
|
|
3ccae93757 | ||
|
|
450dec2baf | ||
|
|
36121527ca | ||
|
|
f30451f7e1 | ||
|
|
74d7f63f64 | ||
|
|
350a4ab762 | ||
|
|
0e0e75b89c | ||
|
|
e76440d928 | ||
|
|
0cbace67a5 | ||
|
|
84a3082589 | ||
|
|
cdfcca06d1 | ||
|
|
630bfa4cee | ||
|
|
90cb7beb1f | ||
|
|
d37fa33278 | ||
|
|
eed9c906cc | ||
|
|
2b7ed5f4dd | ||
|
|
6a7a309a2b | ||
|
|
0382069995 | ||
|
|
7c5f40d7d1 | ||
|
|
09e9530660 | ||
|
|
947d25b335 | ||
|
|
80cbc6699a | ||
|
|
da0933fa92 | ||
|
|
9ad217ca55 | ||
|
|
adc9f32fb3 | ||
|
|
970216dd59 | ||
|
|
1a0bc92bde | ||
|
|
e4709ed6fe | ||
|
|
21038212c1 | ||
|
|
b15471b4ff | ||
|
|
ff17b60f56 | ||
|
|
3284578445 | ||
|
|
4b670a59a3 | ||
|
|
3f928cf958 | ||
|
|
01929c2288 | ||
|
|
2634433b2c | ||
|
|
cb2bc17283 | ||
|
|
efebbc059a | ||
|
|
6e860d293a | ||
|
|
c0a9a95e4f |
@@ -12,7 +12,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 14
|
java-version: 16
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
@@ -25,12 +25,13 @@ jobs:
|
|||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: mvn -B install --file main/pom.xml -Pcoverage
|
run: mvn -B install --file main/pom.xml -Pcoverage
|
||||||
- name: Run Codacy Coverage Reporter
|
- name: Run Codacy Coverage Reporter
|
||||||
|
if: github.repository == 'cryptomator/cryptomator'
|
||||||
run: |
|
run: |
|
||||||
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
$JAVA_HOME/bin/java --illegal-access=permit -jar ~/codacy-coverage-reporter.jar final
|
||||||
env:
|
env:
|
||||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
- name: Assemble buildkit-linux.zip
|
- name: Assemble buildkit-linux.zip
|
||||||
@@ -59,7 +60,7 @@ jobs:
|
|||||||
name: Draft a Release on GitHub Releases
|
name: Draft a Release on GitHub Releases
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
|
||||||
steps:
|
steps:
|
||||||
- name: Download buildkit-linux.zip
|
- name: Download buildkit-linux.zip
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
@@ -86,6 +87,21 @@ jobs:
|
|||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
body: |
|
body: |
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
* [ ] add Linux appimage, zsync file and signature file
|
||||||
|
* [ ] add Windows installer and signature file
|
||||||
|
* [ ] add MacOs disk image and signature file
|
||||||
|
|
||||||
|
## What's new
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
:scroll: A complete list of closed issues is available [here](LINK)
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
- name: Upload buildkit-linux.zip to GitHub Releases
|
- name: Upload buildkit-linux.zip to GitHub Releases
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ pom.xml.versionsBackup
|
|||||||
.idea/compiler.xml
|
.idea/compiler.xml
|
||||||
.idea/encodings.xml
|
.idea/encodings.xml
|
||||||
.idea/jarRepositories.xml
|
.idea/jarRepositories.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
.idea/**/libraries/
|
.idea/**/libraries/
|
||||||
*.iml
|
*.iml
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="false" project-jdk-name="14" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||||
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
||||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/gh/cryptomator/cryptomator/dashboard)
|
||||||
[](http://twitter.com/Cryptomator)
|
[](http://twitter.com/Cryptomator)
|
||||||
[](https://translate.cryptomator.org/)
|
[](https://translate.cryptomator.org/)
|
||||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||||
@@ -17,11 +17,26 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
|
|
||||||
### Gold Sponsors
|
### Gold Sponsors
|
||||||
|
|
||||||
[<img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="96">](https://www.gee-whiz.de/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="80"></a></td>
|
||||||
|
<td><a href="https://proxy-hub.com/"><img src="https://cryptomator.org/img/sponsors/proxyhub.svg" alt="Proxy-Hub" height="80"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
### Silver Sponsors
|
### Silver Sponsors
|
||||||
|
|
||||||
[](https://thebestvpn.com/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://thebestvpn.com/"><img src="https://cryptomator.org/img/sponsors/thebestvpn@2x.png" alt="TheBestVPN" height="64"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
- [Jameson Lopp](https://www.lopp.net/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,7 +48,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
- Works with Dropbox, Google Drive, OneDrive, MEGA, pCloud, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
||||||
- Open Source means: No backdoors, control is better than trust
|
- Open Source means: No backdoors, control is better than trust
|
||||||
- Client-side: No accounts, no data shared with any online service
|
- Client-side: No accounts, no data shared with any online service
|
||||||
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
||||||
@@ -65,7 +80,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 14 (e.g. adoptopenjdk)
|
* JDK 16 (e.g. adoptopenjdk)
|
||||||
* Maven 3
|
* Maven 3
|
||||||
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.12</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.12</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.cryptomator.common.settings.SettingsProvider;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -25,6 +26,8 @@ import javafx.beans.binding.Binding;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -47,10 +50,28 @@ public abstract class CommonsModule {
|
|||||||
@Named("licensePublicKey")
|
@Named("licensePublicKey")
|
||||||
static String provideLicensePublicKey() {
|
static String provideLicensePublicKey() {
|
||||||
// in PEM format without the dash-escaped begin/end lines
|
// in PEM format without the dash-escaped begin/end lines
|
||||||
return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
|
return """
|
||||||
+ "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW\
|
||||||
+ "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
|
fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV\
|
||||||
+ "r0DzRyj4ixPIt38CQB8=";
|
gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE\
|
||||||
|
r0DzRyj4ixPIt38CQB8=\
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static SecureRandom provideCSPRNG() {
|
||||||
|
try {
|
||||||
|
return SecureRandom.getInstanceStrong();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static MasterkeyFileAccess provideMasterkeyFileAccess(SecureRandom csprng) {
|
||||||
|
return new MasterkeyFileAccess(Constants.PEPPER, csprng);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -3,5 +3,8 @@ package org.cryptomator.common;
|
|||||||
public interface Constants {
|
public interface Constants {
|
||||||
|
|
||||||
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||||
|
String MASTERKEY_BACKUP_SUFFIX = ".bkup";
|
||||||
|
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
||||||
|
byte[] PEPPER = new byte[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class LicenseChecker {
|
|||||||
try {
|
try {
|
||||||
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
||||||
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||||
if (key instanceof ECPublicKey) {
|
if (key instanceof ECPublicKey k) {
|
||||||
return (ECPublicKey) key;
|
return k;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Key not an EC public key.");
|
throw new IllegalStateException("Key not an EC public key.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ public class KeychainManager implements KeychainAccessProvider {
|
|||||||
return keychain.getValue() != null;
|
return keychain.getValue() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return keychain.getValue() == null || keychain.get().isLocked();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the keychain knows a passphrase for the given key.
|
* Checks if the keychain knows a passphrase for the given key.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public enum UiTheme {
|
|||||||
AUTOMATIC("preferences.general.theme.automatic");
|
AUTOMATIC("preferences.general.theme.automatic");
|
||||||
|
|
||||||
public static UiTheme[] applicableValues() {
|
public static UiTheme[] applicableValues() {
|
||||||
if (SystemUtils.IS_OS_MAC) {
|
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
|
||||||
return values();
|
return values();
|
||||||
} else {
|
} else {
|
||||||
return new UiTheme[]{LIGHT, DARK};
|
return new UiTheme[]{LIGHT, DARK};
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The settings specific to a single vault.
|
* The settings specific to a single vault.
|
||||||
@@ -37,7 +35,7 @@ public class VaultSettings {
|
|||||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||||
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
||||||
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||||
|
|
||||||
private static final Random RNG = new Random();
|
private static final Random RNG = new Random();
|
||||||
@@ -52,7 +50,7 @@ public class VaultSettings {
|
|||||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||||
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
|
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||||
|
|
||||||
private final StringBinding mountName;
|
private final StringBinding mountName;
|
||||||
@@ -63,7 +61,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
|
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -152,8 +150,8 @@ public class VaultSettings {
|
|||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty filenameLengthLimit() {
|
public IntegerProperty maxCleartextFilenameLength() {
|
||||||
return filenameLengthLimit;
|
return maxCleartextFilenameLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
||||||
@@ -173,8 +171,7 @@ public class VaultSettings {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof VaultSettings && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof VaultSettings other && obj.getClass().equals(this.getClass())) {
|
||||||
VaultSettings other = (VaultSettings) obj;
|
|
||||||
return Objects.equals(this.id, other.id);
|
return Objects.equals(this.id, other.id);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+4
-4
@@ -29,7 +29,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("customMountPath").value(value.customMountPath().get());
|
out.name("customMountPath").value(value.customMountPath().get());
|
||||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.name("mountFlags").value(value.mountFlags().get());
|
out.name("mountFlags").value(value.mountFlags().get());
|
||||||
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||||
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
@@ -64,7 +64,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||||
case "mountFlags" -> mountFlags = in.nextString();
|
case "mountFlags" -> mountFlags = in.nextString();
|
||||||
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
|
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
@@ -88,7 +88,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.customMountPath().set(customMountPath);
|
vaultSettings.customMountPath().set(customMountPath);
|
||||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
vaultSettings.mountFlags().set(mountFlags);
|
||||||
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import org.cryptomator.common.mountpoint.MountPointChooser;
|
|||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
|
||||||
import org.cryptomator.frontend.dokany.Mount;
|
import org.cryptomator.frontend.dokany.Mount;
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
import org.cryptomator.frontend.dokany.MountFactory;
|
||||||
import org.cryptomator.frontend.dokany.MountFailedException;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class DokanyVolume extends AbstractVolume {
|
public class DokanyVolume extends AbstractVolume {
|
||||||
|
|
||||||
@@ -22,15 +22,13 @@ public class DokanyVolume extends AbstractVolume {
|
|||||||
private static final String FS_TYPE_NAME = "CryptomatorFS";
|
private static final String FS_TYPE_NAME = "CryptomatorFS";
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final MountFactory mountFactory;
|
|
||||||
|
|
||||||
private Mount mount;
|
private Mount mount;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
public DokanyVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
||||||
super(choosers);
|
super(choosers);
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.mountFactory = new MountFactory(executorService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,11 +37,11 @@ public class DokanyVolume extends AbstractVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
||||||
this.mountPoint = determineMountPoint();
|
this.mountPoint = determineMountPoint();
|
||||||
try {
|
try {
|
||||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
|
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
|
||||||
} catch (MountFailedException e) {
|
} catch (DokanyMountFailedException e) {
|
||||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
if (vaultSettings.getCustomMountPath().isPresent()) {
|
||||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
|||||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.FuseMountException;
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
||||||
import org.cryptomator.frontend.fuse.mount.Mount;
|
import org.cryptomator.frontend.fuse.mount.Mount;
|
||||||
@@ -20,6 +20,7 @@ import javax.inject.Named;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FuseVolume extends AbstractVolume {
|
public class FuseVolume extends AbstractVolume {
|
||||||
@@ -35,20 +36,21 @@ public class FuseVolume extends AbstractVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
||||||
this.mountPoint = determineMountPoint();
|
this.mountPoint = determineMountPoint();
|
||||||
|
mount(fs.getPath("/"), mountFlags, onExitAction);
|
||||||
mount(fs.getPath("/"), mountFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mount(Path root, String mountFlags) throws VolumeException {
|
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
Mounter mounter = FuseMountFactory.getMounter();
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
.withFlags(splitFlags(mountFlags)) //
|
||||||
|
.withMountPoint(mountPoint) //
|
||||||
|
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
|
||||||
.build();
|
.build();
|
||||||
this.mount = mounter.mount(root, envVars);
|
this.mount = mounter.mount(root, envVars, onExitAction);
|
||||||
} catch (CommandFailedException | FuseNotSupportedException e) {
|
} catch ( FuseMountException | FuseNotSupportedException e) {
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,8 +91,7 @@ public class FuseVolume extends AbstractVolume {
|
|||||||
public synchronized void unmountForced() throws VolumeException {
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
mount.unmountForced();
|
mount.unmountForced();
|
||||||
mount.close();
|
} catch (FuseMountException e) {
|
||||||
} catch (CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupMountPoint();
|
cleanupMountPoint();
|
||||||
@@ -100,8 +101,7 @@ public class FuseVolume extends AbstractVolume {
|
|||||||
public synchronized void unmount() throws VolumeException {
|
public synchronized void unmount() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
mount.unmount();
|
mount.unmount();
|
||||||
mount.close();
|
} catch (FuseMountException e) {
|
||||||
} catch (CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupMountPoint();
|
cleanupMountPoint();
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
public class LockNotCompletedException extends Exception {
|
||||||
|
|
||||||
|
public LockNotCompletedException(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockNotCompletedException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,10 +17,12 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
|
|||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
import org.cryptomator.cryptofs.common.Constants;
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
|
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -35,28 +37,29 @@ import javafx.beans.property.BooleanProperty;
|
|||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||||
|
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Provider<Volume> volumeProvider;
|
private final Provider<Volume> volumeProvider;
|
||||||
private final StringBinding defaultMountFlags;
|
private final StringBinding defaultMountFlags;
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final ObjectProperty<VaultState> state;
|
private final VaultState state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayName;
|
private final StringBinding displayName;
|
||||||
@@ -74,7 +77,7 @@ public class Vault {
|
|||||||
private volatile Volume volume;
|
private volatile Volume volume;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.volumeProvider = volumeProvider;
|
this.volumeProvider = volumeProvider;
|
||||||
this.defaultMountFlags = defaultMountFlags;
|
this.defaultMountFlags = defaultMountFlags;
|
||||||
@@ -99,24 +102,31 @@ public class Vault {
|
|||||||
// Commands
|
// Commands
|
||||||
// ********************************************************************************/
|
// ********************************************************************************/
|
||||||
|
|
||||||
private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
|
||||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
flags.add(FileSystemFlags.READONLY);
|
flags.add(FileSystemFlags.READONLY);
|
||||||
|
} else if(vaultSettings.maxCleartextFilenameLength().get() == -1) {
|
||||||
|
LOG.debug("Determining cleartext filename length limitations...");
|
||||||
|
var checker = new FileSystemCapabilityChecker();
|
||||||
|
int shorteningThreshold = getUnverifiedVaultConfig().orElseThrow().allegedShorteningThreshold();
|
||||||
|
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
|
||||||
|
if (ciphertextLimit < shorteningThreshold) {
|
||||||
|
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
|
||||||
|
vaultSettings.maxCleartextFilenameLength().set(cleartextLimit);
|
||||||
|
} else {
|
||||||
|
vaultSettings.maxCleartextFilenameLength().setValue(UNLIMITED_FILENAME_LENGTH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!flags.contains(FileSystemFlags.READONLY) && vaultSettings.filenameLengthLimit().get() == -1) {
|
|
||||||
LOG.debug("Determining file name length limitations...");
|
if (vaultSettings.maxCleartextFilenameLength().get() < UNLIMITED_FILENAME_LENGTH) {
|
||||||
int limit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(getPath());
|
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength().get());
|
||||||
vaultSettings.filenameLengthLimit().set(limit);
|
|
||||||
LOG.info("Storing file name length limit of {}", limit);
|
|
||||||
}
|
}
|
||||||
assert vaultSettings.filenameLengthLimit().get() > 0;
|
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||||
.withPassphrase(passphrase) //
|
.withKeyLoader(keyLoader) //
|
||||||
.withFlags(flags) //
|
.withFlags(flags) //
|
||||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
|
||||||
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
|
|
||||||
.withMaxNameLength(vaultSettings.filenameLengthLimit().get()) //
|
|
||||||
.build();
|
.build();
|
||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
@@ -133,29 +143,51 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
||||||
if (cryptoFileSystem.get() == null) {
|
if (cryptoFileSystem.get() != null) {
|
||||||
CryptoFileSystem fs = createCryptoFileSystem(passphrase);
|
|
||||||
cryptoFileSystem.set(fs);
|
|
||||||
try {
|
|
||||||
volume = volumeProvider.get();
|
|
||||||
volume.mount(fs, getEffectiveMountFlags());
|
|
||||||
} catch (Exception e) {
|
|
||||||
destroyCryptoFileSystem();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Already unlocked.");
|
throw new IllegalStateException("Already unlocked.");
|
||||||
}
|
}
|
||||||
|
CryptoFileSystem fs = createCryptoFileSystem(keyLoader);
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
cryptoFileSystem.set(fs);
|
||||||
|
volume = volumeProvider.get();
|
||||||
|
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws VolumeException {
|
private void lockOnVolumeExit(Throwable t) {
|
||||||
|
LOG.info("Unmounted vault '{}'", getDisplayName());
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
state.set(VaultState.Value.LOCKED);
|
||||||
|
if (t != null) {
|
||||||
|
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
|
||||||
|
//initiate unmount
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
volume.unmountForced();
|
volume.unmountForced();
|
||||||
} else {
|
} else {
|
||||||
volume.unmount();
|
volume.unmount();
|
||||||
}
|
}
|
||||||
destroyCryptoFileSystem();
|
|
||||||
|
//wait for lockOnVolumeExit to be executed
|
||||||
|
try {
|
||||||
|
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new LockNotCompletedException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
||||||
@@ -166,16 +198,12 @@ public class Vault {
|
|||||||
// Observable Properties
|
// Observable Properties
|
||||||
// *******************************************************************************
|
// *******************************************************************************
|
||||||
|
|
||||||
public ObjectProperty<VaultState> stateProperty() {
|
public VaultState stateProperty() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultState getState() {
|
public VaultState.Value getState() {
|
||||||
return state.get();
|
return state.getValue();
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(VaultState value) {
|
|
||||||
state.setValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<Exception> lastKnownExceptionProperty() {
|
public ObjectProperty<Exception> lastKnownExceptionProperty() {
|
||||||
@@ -195,7 +223,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLocked() {
|
public boolean isLocked() {
|
||||||
return state.get() == VaultState.LOCKED;
|
return state.get() == VaultState.Value.LOCKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding processingProperty() {
|
public BooleanBinding processingProperty() {
|
||||||
@@ -203,7 +231,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isProcessing() {
|
public boolean isProcessing() {
|
||||||
return state.get() == VaultState.PROCESSING;
|
return state.get() == VaultState.Value.PROCESSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding unlockedProperty() {
|
public BooleanBinding unlockedProperty() {
|
||||||
@@ -211,7 +239,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnlocked() {
|
public boolean isUnlocked() {
|
||||||
return state.get() == VaultState.UNLOCKED;
|
return state.get() == VaultState.Value.UNLOCKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding missingProperty() {
|
public BooleanBinding missingProperty() {
|
||||||
@@ -219,7 +247,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMissing() {
|
public boolean isMissing() {
|
||||||
return state.get() == VaultState.MISSING;
|
return state.get() == VaultState.Value.MISSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding needsMigrationProperty() {
|
public BooleanBinding needsMigrationProperty() {
|
||||||
@@ -227,7 +255,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNeedsMigration() {
|
public boolean isNeedsMigration() {
|
||||||
return state.get() == VaultState.NEEDS_MIGRATION;
|
return state.get() == VaultState.Value.NEEDS_MIGRATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding unknownErrorProperty() {
|
public BooleanBinding unknownErrorProperty() {
|
||||||
@@ -235,7 +263,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnknownError() {
|
public boolean isUnknownError() {
|
||||||
return state.get() == VaultState.ERROR;
|
return state.get() == VaultState.Value.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayNameProperty() {
|
public StringBinding displayNameProperty() {
|
||||||
@@ -251,7 +279,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessPoint() {
|
public String getAccessPoint() {
|
||||||
if (state.get() == VaultState.UNLOCKED) {
|
if (state.getValue() == VaultState.Value.UNLOCKED) {
|
||||||
assert volume != null;
|
assert volume != null;
|
||||||
return volume.getMountPoint().orElse(Path.of("")).toString();
|
return volume.getMountPoint().orElse(Path.of("")).toString();
|
||||||
} else {
|
} else {
|
||||||
@@ -299,6 +327,16 @@ public class Vault {
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<UnverifiedVaultConfig> getUnverifiedVaultConfig() {
|
||||||
|
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
|
||||||
|
try {
|
||||||
|
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
|
||||||
|
return Optional.of(VaultConfig.decode(token));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Observable[] observables() {
|
public Observable[] observables() {
|
||||||
return new Observable[]{state};
|
return new Observable[]{state};
|
||||||
}
|
}
|
||||||
@@ -355,8 +393,7 @@ public class Vault {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof Vault && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof Vault other && obj.getClass().equals(this.getClass())) {
|
||||||
final Vault other = (Vault) obj;
|
|
||||||
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public interface VaultComponent {
|
|||||||
Builder vaultSettings(VaultSettings vaultSettings);
|
Builder vaultSettings(VaultSettings vaultSettings);
|
||||||
|
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
Builder initialVaultState(VaultState vaultState);
|
Builder initialVaultState(VaultState.Value vaultState);
|
||||||
|
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
|
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
|
||||||
|
|||||||
+2
-2
@@ -22,10 +22,10 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
|
|||||||
public void onChanged(Change<? extends Vault> c) {
|
public void onChanged(Change<? extends Vault> c) {
|
||||||
while (c.next()) {
|
while (c.next()) {
|
||||||
if (c.wasAdded()) {
|
if (c.wasAdded()) {
|
||||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||||
} else if (c.wasRemoved()) {
|
} else if (c.wasRemoved()) {
|
||||||
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.removeAll(removedSettings);
|
vaultSettingsList.removeAll(removedSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package org.cryptomator.common.vaults;
|
|||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptofs.DirStructure;
|
||||||
import org.cryptomator.cryptofs.migration.Migrators;
|
import org.cryptomator.cryptofs.migration.Migrators;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -20,14 +21,16 @@ import javax.inject.Singleton;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class VaultListManager {
|
public class VaultListManager {
|
||||||
@@ -52,19 +55,18 @@ public class VaultListManager {
|
|||||||
return vaultList;
|
return vaultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vault add(Path pathToVault) throws NoSuchFileException {
|
public Vault add(Path pathToVault) throws IOException {
|
||||||
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
||||||
if (!CryptoFileSystemProvider.containsVault(normalizedPathToVault, MASTERKEY_FILENAME)) {
|
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
||||||
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
|
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
|
||||||
}
|
}
|
||||||
Optional<Vault> alreadyExistingVault = get(normalizedPathToVault);
|
|
||||||
if (alreadyExistingVault.isPresent()) {
|
return get(normalizedPathToVault) //
|
||||||
return alreadyExistingVault.get();
|
.orElseGet(() -> {
|
||||||
} else {
|
Vault newVault = create(newVaultSettings(normalizedPathToVault));
|
||||||
Vault newVault = create(newVaultSettings(normalizedPathToVault));
|
vaultList.add(newVault);
|
||||||
vaultList.add(newVault);
|
return newVault;
|
||||||
return newVault;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private VaultSettings newVaultSettings(Path path) {
|
private VaultSettings newVaultSettings(Path path) {
|
||||||
@@ -79,7 +81,7 @@ public class VaultListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addAll(Collection<VaultSettings> vaultSettings) {
|
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
Collection<Vault> vaults = vaultSettings.stream().map(this::create).toList();
|
||||||
vaultList.addAll(vaults);
|
vaultList.addAll(vaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,43 +96,45 @@ public class VaultListManager {
|
|||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
||||||
try {
|
try {
|
||||||
VaultState vaultState = determineVaultState(vaultSettings.path().get());
|
VaultState.Value vaultState = determineVaultState(vaultSettings.path().get());
|
||||||
compBuilder.initialVaultState(vaultState);
|
compBuilder.initialVaultState(vaultState);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
|
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
|
||||||
compBuilder.initialVaultState(VaultState.ERROR);
|
compBuilder.initialVaultState(ERROR);
|
||||||
compBuilder.initialErrorCause(e);
|
compBuilder.initialErrorCause(e);
|
||||||
}
|
}
|
||||||
return compBuilder.build().vault();
|
return compBuilder.build().vault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultState redetermineVaultState(Vault vault) {
|
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||||
VaultState previousState = vault.getState();
|
VaultState state = vault.stateProperty();
|
||||||
|
VaultState.Value previousState = state.getValue();
|
||||||
return switch (previousState) {
|
return switch (previousState) {
|
||||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||||
try {
|
try {
|
||||||
VaultState determinedState = determineVaultState(vault.getPath());
|
var determinedState = determineVaultState(vault.getPath());
|
||||||
vault.setState(determinedState);
|
state.set(determinedState);
|
||||||
yield determinedState;
|
yield determinedState;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||||
vault.setState(VaultState.ERROR);
|
state.set(ERROR);
|
||||||
vault.setLastKnownException(e);
|
vault.setLastKnownException(e);
|
||||||
yield VaultState.ERROR;
|
yield ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VaultState determineVaultState(Path pathToVault) throws IOException {
|
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
if (!Files.exists(pathToVault)) {
|
||||||
return VaultState.MISSING;
|
return VaultState.Value.MISSING;
|
||||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
|
||||||
return VaultState.NEEDS_MIGRATION;
|
|
||||||
} else {
|
|
||||||
return VaultState.LOCKED;
|
|
||||||
}
|
}
|
||||||
|
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||||
|
case VAULT -> VaultState.Value.LOCKED;
|
||||||
|
case UNRELATED -> VaultState.Value.MISSING;
|
||||||
|
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? VaultState.Value.NEEDS_MIGRATION : VaultState.Value.MISSING;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,12 +40,6 @@ public class VaultModule {
|
|||||||
return new AtomicReference<>();
|
return new AtomicReference<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
public ObjectProperty<VaultState> provideVaultState(VaultState initialState) {
|
|
||||||
return new SimpleObjectProperty<>(initialState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("lastKnownException")
|
@Named("lastKnownException")
|
||||||
@PerVault
|
@PerVault
|
||||||
@@ -53,7 +47,6 @@ public class VaultModule {
|
|||||||
return new SimpleObjectProperty<>(initialErrorCause);
|
return new SimpleObjectProperty<>(initialErrorCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
||||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
||||||
|
|||||||
@@ -1,34 +1,141 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
public enum VaultState {
|
import com.google.common.base.Preconditions;
|
||||||
/**
|
import org.slf4j.Logger;
|
||||||
* No vault found at the provided path
|
import org.slf4j.LoggerFactory;
|
||||||
*/
|
|
||||||
MISSING,
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ObservableObjectValue;
|
||||||
|
import javafx.beans.value.ObservableValueBase;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@PerVault
|
||||||
|
public class VaultState extends ObservableValueBase<VaultState.Value> implements ObservableObjectValue<VaultState.Value> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultState.class);
|
||||||
|
|
||||||
|
public enum Value {
|
||||||
|
/**
|
||||||
|
* No vault found at the provided path
|
||||||
|
*/
|
||||||
|
MISSING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vault requires migration to a newer vault format
|
||||||
|
*/
|
||||||
|
NEEDS_MIGRATION,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vault ready to be unlocked
|
||||||
|
*/
|
||||||
|
LOCKED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vault in transition between two other states
|
||||||
|
*/
|
||||||
|
PROCESSING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vault is unlocked
|
||||||
|
*/
|
||||||
|
UNLOCKED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown state due to preceeding unrecoverable exceptions.
|
||||||
|
*/
|
||||||
|
ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicReference<Value> value;
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
private final Condition valueChanged = lock.newCondition();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public VaultState(VaultState.Value initialValue) {
|
||||||
|
this.value = new AtomicReference<>(initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value get() {
|
||||||
|
return getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value getValue() {
|
||||||
|
return value.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault requires migration to a newer vault format
|
* Transitions from <code>fromState</code> to <code>toState</code>.
|
||||||
|
*
|
||||||
|
* @param fromState Previous state
|
||||||
|
* @param toState New state
|
||||||
|
* @return <code>true</code> if successful
|
||||||
*/
|
*/
|
||||||
NEEDS_MIGRATION,
|
public boolean transition(Value fromState, Value toState) {
|
||||||
|
Preconditions.checkArgument(fromState != toState, "fromState must be different than toState");
|
||||||
|
boolean success = value.compareAndSet(fromState, toState);
|
||||||
|
if (success) {
|
||||||
|
fireValueChangedEvent();
|
||||||
|
} else {
|
||||||
|
LOG.debug("Failed transiting into state {}: Expected state was not{}.", fromState, toState);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Value newState) {
|
||||||
|
var oldState = value.getAndSet(newState);
|
||||||
|
if (oldState != newState) {
|
||||||
|
fireValueChangedEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault ready to be unlocked
|
* Waits for the specified time, until the desired state is reached.
|
||||||
|
*
|
||||||
|
* @param desiredState what state to wait for
|
||||||
|
* @param time the maximum time to wait
|
||||||
|
* @param unit the time unit of the {@code time} argument
|
||||||
|
* @return {@code false} if the waiting time detectably elapsed before reaching {@code desiredState}
|
||||||
|
* @throws InterruptedException if the current thread is interrupted
|
||||||
*/
|
*/
|
||||||
LOCKED,
|
public boolean awaitState(Value desiredState, long time, TimeUnit unit) throws InterruptedException {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
long remaining = TimeUnit.NANOSECONDS.convert(time, unit);
|
||||||
|
while (value.get() != desiredState) {
|
||||||
|
if (remaining <= 0L) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
remaining = valueChanged.awaitNanos(remaining);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private void signal() {
|
||||||
* Vault in transition between two other states
|
lock.lock();
|
||||||
*/
|
try {
|
||||||
PROCESSING,
|
valueChanged.signalAll();
|
||||||
|
} finally {
|
||||||
/**
|
lock.unlock();
|
||||||
* Vault is unlocked
|
}
|
||||||
*/
|
}
|
||||||
UNLOCKED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown state due to preceeding unrecoverable exceptions.
|
|
||||||
*/
|
|
||||||
ERROR;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void fireValueChangedEvent() {
|
||||||
|
signal();
|
||||||
|
if (Platform.isFxApplicationThread()) {
|
||||||
|
super.fireValueChangedEvent();
|
||||||
|
} else {
|
||||||
|
Platform.runLater(super::fireValueChangedEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class VaultStats {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
|
||||||
|
|
||||||
private final AtomicReference<CryptoFileSystem> fs;
|
private final AtomicReference<CryptoFileSystem> fs;
|
||||||
private final ObjectProperty<VaultState> state;
|
private final VaultState state;
|
||||||
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
||||||
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
||||||
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
||||||
@@ -41,7 +41,7 @@ public class VaultStats {
|
|||||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
VaultStats(AtomicReference<CryptoFileSystem> fs, VaultState state, ExecutorService executor) {
|
||||||
this.fs = fs;
|
this.fs = fs;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.updateService = new UpdateStatsService();
|
this.updateService = new UpdateStatsService();
|
||||||
@@ -52,13 +52,13 @@ public class VaultStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
|
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
if (VaultState.UNLOCKED.equals(state.get())) {
|
if (VaultState.Value.UNLOCKED == state.get()) {
|
||||||
assert fs.get() != null;
|
assert fs.get() != null;
|
||||||
LOG.debug("start recording stats");
|
LOG.debug("start recording stats");
|
||||||
updateService.restart();
|
Platform.runLater(() -> updateService.restart());
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("stop recording stats");
|
LOG.debug("stop recording stats");
|
||||||
updateService.cancel();
|
Platform.runLater(() -> updateService.cancel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +34,7 @@ public interface Volume {
|
|||||||
* @param fs
|
* @param fs
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
|
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reveals the mounted volume.
|
* Reveals the mounted volume.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
public class WebDavVolume implements Volume {
|
||||||
@@ -31,6 +32,7 @@ public class WebDavVolume implements Volume {
|
|||||||
private WebDavServer server;
|
private WebDavServer server;
|
||||||
private WebDavServletController servlet;
|
private WebDavServletController servlet;
|
||||||
private Mounter.Mount mount;
|
private Mounter.Mount mount;
|
||||||
|
private Consumer<Throwable> onExitAction;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
||||||
@@ -41,12 +43,13 @@ public class WebDavVolume implements Volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
||||||
startServlet(fs);
|
startServlet(fs);
|
||||||
mountServlet();
|
mountServlet();
|
||||||
|
this.onExitAction = onExitAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startServlet(CryptoFileSystem fs){
|
private void startServlet(CryptoFileSystem fs) {
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
server = serverProvider.get();
|
server = serverProvider.get();
|
||||||
}
|
}
|
||||||
@@ -66,7 +69,7 @@ public class WebDavVolume implements Volume {
|
|||||||
|
|
||||||
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specifc one or there is no free.
|
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specifc one or there is no free.
|
||||||
Supplier<String> driveLetterSupplier;
|
Supplier<String> driveLetterSupplier;
|
||||||
if(System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
||||||
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
||||||
} else {
|
} else {
|
||||||
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
||||||
@@ -101,6 +104,7 @@ public class WebDavVolume implements Volume {
|
|||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
onExitAction.accept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,6 +115,7 @@ public class WebDavVolume implements Volume {
|
|||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
onExitAction.accept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class EnvironmentTest {
|
|||||||
public void testSettingsPath() {
|
public void testSettingsPath() {
|
||||||
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
||||||
|
|
||||||
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
List<Path> result = env.getSettingsPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||||
@@ -43,7 +43,7 @@ class EnvironmentTest {
|
|||||||
public void testIpcPortPath() {
|
public void testIpcPortPath() {
|
||||||
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
||||||
|
|
||||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
List<Path> result = env.getIpcPortPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||||
@@ -54,7 +54,7 @@ class EnvironmentTest {
|
|||||||
public void testKeychainPath() {
|
public void testKeychainPath() {
|
||||||
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
||||||
|
|
||||||
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
|
List<Path> result = env.getKeychainPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=")
|
@DisplayName("test.path.property=")
|
||||||
public void testEmptyList() {
|
public void testEmptyList() {
|
||||||
System.setProperty("test.path.property", "");
|
System.setProperty("test.path.property", "");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=/foo/bar/test")
|
@DisplayName("test.path.property=/foo/bar/test")
|
||||||
public void testSingleAbsolutePath() {
|
public void testSingleAbsolutePath() {
|
||||||
System.setProperty("test.path.property", "/foo/bar/test");
|
System.setProperty("test.path.property", "/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||||
@@ -107,7 +107,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test")
|
@DisplayName("test.path.property=~/test")
|
||||||
public void testSingleHomeRelativePath() {
|
public void testSingleHomeRelativePath() {
|
||||||
System.setProperty("test.path.property", "~/test");
|
System.setProperty("test.path.property", "~/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
||||||
@@ -117,7 +117,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
||||||
public void testMultiplePaths() {
|
public void testMultiplePaths() {
|
||||||
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import java.util.Optional;
|
|||||||
|
|
||||||
class LicenseCheckerTest {
|
class LicenseCheckerTest {
|
||||||
|
|
||||||
private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
|
private static final String PUBLIC_KEY = """
|
||||||
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\
|
||||||
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\
|
||||||
+ "Al8G7CqwoJOsW7Kddns=";
|
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\
|
||||||
|
Al8G7CqwoJOsW7Kddns=\
|
||||||
|
""";
|
||||||
|
|
||||||
private LicenseChecker licenseChecker;
|
private LicenseChecker licenseChecker;
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,9 @@ class MapKeychainAccess implements KeychainAccessProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-7
@@ -21,13 +21,18 @@ public class SettingsJsonAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserialize() throws IOException {
|
public void testDeserialize() throws IOException {
|
||||||
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
|
String json = """
|
||||||
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
|
{
|
||||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
"directories": [
|
||||||
+ "\"checkForUpdatesEnabled\": true,"//
|
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
|
||||||
+ "\"port\": 8080,"//
|
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
|
||||||
+ "\"numTrayNotifications\": 42,"//
|
],
|
||||||
+ "\"preferredVolumeImpl\": \"FUSE\"}";
|
"checkForUpdatesEnabled": true,
|
||||||
|
"port": 8080,
|
||||||
|
"numTrayNotifications": 42,
|
||||||
|
"preferredVolumeImpl": "FUSE"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
Settings settings = adapter.fromJson(json);
|
Settings settings = adapter.fromJson(json);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.12</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class FileOpenRequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openFiles(OpenFilesEvent evt) {
|
private void openFiles(OpenFilesEvent evt) {
|
||||||
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
|
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList();
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ class FileOpenRequestHandler {
|
|||||||
LOG.trace("Argument not a valid path: {}", str);
|
LOG.trace("Argument not a valid path: {}", str);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
}).filter(Objects::nonNull).toList();
|
||||||
if (!pathsToOpen.isEmpty()) {
|
if (!pathsToOpen.isEmpty()) {
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ public class LoggerModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
static LoggerContext provideLoggerContext() {
|
static LoggerContext provideLoggerContext() {
|
||||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||||
if (loggerFactory instanceof LoggerContext) {
|
if (loggerFactory instanceof LoggerContext context) {
|
||||||
return (LoggerContext) loggerFactory;
|
return context;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("SLF4J not bound to Logback.");
|
throw new IllegalStateException("SLF4J not bound to Logback.");
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-29
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.12</version>
|
<version>1.6.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -22,41 +22,35 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.jdk.version>16</project.jdk.version>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>2.0.0-rc2</cryptomator.cryptofs.version>
|
||||||
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
|
<cryptomator.integrations.version>1.0.0-beta2</cryptomator.integrations.version>
|
||||||
<cryptomator.integrations.win.version>0.2.1</cryptomator.integrations.win.version>
|
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
|
||||||
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
|
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.integrations.linux.version>0.1.1</cryptomator.integrations.linux.version>
|
<cryptomator.integrations.linux.version>1.0.0-beta1</cryptomator.integrations.linux.version>
|
||||||
<cryptomator.fuse.version>1.2.8</cryptomator.fuse.version>
|
<cryptomator.fuse.version>1.3.1</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.2.3</cryptomator.dokany.version>
|
<cryptomator.dokany.version>1.3.1</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.1.1</cryptomator.webdav.version>
|
<cryptomator.webdav.version>1.2.0</cryptomator.webdav.version>
|
||||||
|
|
||||||
<!-- 3rd party dependencies -->
|
<!-- 3rd party dependencies -->
|
||||||
<javafx.version>15</javafx.version>
|
<javafx.version>16</javafx.version>
|
||||||
<commons-lang3.version>3.11</commons-lang3.version>
|
<commons-lang3.version>3.11</commons-lang3.version>
|
||||||
<jwt.version>3.12.0</jwt.version>
|
<jwt.version>3.15.0</jwt.version>
|
||||||
<easybind.version>2.1.0</easybind.version>
|
<easybind.version>2.1.0</easybind.version>
|
||||||
<guava.version>30.0-jre</guava.version>
|
<guava.version>30.1.1-jre</guava.version>
|
||||||
<dagger.version>2.29.1</dagger.version>
|
<dagger.version>2.35.1</dagger.version>
|
||||||
<gson.version>2.8.6</gson.version>
|
<gson.version>2.8.6</gson.version>
|
||||||
<slf4j.version>1.7.30</slf4j.version>
|
<slf4j.version>1.7.30</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.7.0</junit.jupiter.version>
|
<junit.jupiter.version>5.7.1</junit.jupiter.version>
|
||||||
<mockito.version>3.6.0</mockito.version>
|
<mockito.version>3.9.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>jcenter</id>
|
|
||||||
<url>https://jcenter.bintray.com</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- modules -->
|
<!-- modules -->
|
||||||
@@ -82,6 +76,12 @@
|
|||||||
<artifactId>cryptofs</artifactId>
|
<artifactId>cryptofs</artifactId>
|
||||||
<version>${cryptomator.cryptofs.version}</version>
|
<version>${cryptomator.cryptofs.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--TODO: only temporary workaround until 1.6.0-beta -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>cryptolib</artifactId>
|
||||||
|
<version>2.0.0-rc1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>fuse-nio-adapter</artifactId>
|
<artifactId>fuse-nio-adapter</artifactId>
|
||||||
@@ -224,12 +224,6 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- TODO: temporary fix for XXE attack, can be removed once java-jwt is updated -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<version>2.10.5.1</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -437,7 +431,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>14</release>
|
<release>${project.jdk.version}</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- This file lists false positives found by org.owasp:dependency-check-maven build plugin -->
|
<!-- This file lists false positives found by org.owasp:dependency-check-maven build plugin -->
|
||||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd">
|
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd">
|
||||||
<suppress>
|
|
||||||
<notes><![CDATA[ Upstream fix backported from 2.11.0 to 2.10.5.1, see https://github.com/FasterXML/jackson-databind/issues/2589#issuecomment-714833837. ]]></notes>
|
|
||||||
<gav>com.fasterxml.jackson.core:jackson-databind:2.10.5.1</gav>
|
|
||||||
<cve>CVE-2020-25649</cve>
|
|
||||||
</suppress>
|
|
||||||
<suppress>
|
<suppress>
|
||||||
<notes><![CDATA[ Suppress known vulnerabilities in FUSE libraries for fuse-nio-adapter. For more info, see suppression.xml of https://github.com/cryptomator/fuse-nio-adapter ]]></notes>
|
<notes><![CDATA[ Suppress known vulnerabilities in FUSE libraries for fuse-nio-adapter. For more info, see suppression.xml of https://github.com/cryptomator/fuse-nio-adapter ]]></notes>
|
||||||
<gav regex="true">^org\.cryptomator:fuse-nio-adapter:.*$</gav>
|
<gav regex="true">^org\.cryptomator:fuse-nio-adapter:.*$</gav>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.12</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -43,8 +43,8 @@ public abstract class AddVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@AddVaultWizardWindow
|
@AddVaultWizardWindow
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -91,50 +91,50 @@ public abstract class AddVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideWelcomeScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ public class ChooseExistingVaultController implements FxController {
|
|||||||
Vault newVault = vaultListManager.add(vaultPath.get());
|
Vault newVault = vaultListManager.add(vaultPath.get());
|
||||||
vault.set(newVault);
|
vault.set(newVault);
|
||||||
window.setScene(successScene.get());
|
window.setScene(successScene.get());
|
||||||
} catch (NoSuchFileException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to open existing vault.", e);
|
LOG.error("Failed to open existing vault.", e);
|
||||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-43
@@ -1,10 +1,10 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
package org.cryptomator.ui.addvaultwizard;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -15,21 +15,21 @@ import javafx.beans.binding.BooleanBinding;
|
|||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.RadioButton;
|
import javafx.scene.control.RadioButton;
|
||||||
import javafx.scene.control.Toggle;
|
import javafx.scene.control.Toggle;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
@@ -43,55 +43,74 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> chooseNameScene;
|
private final Lazy<Scene> chooseNameScene;
|
||||||
private final Lazy<Scene> choosePasswordScene;
|
private final Lazy<Scene> choosePasswordScene;
|
||||||
private final ErrorComponent.Builder errorComponent;
|
|
||||||
private final LocationPresets locationPresets;
|
private final LocationPresets locationPresets;
|
||||||
private final ObjectProperty<Path> vaultPath;
|
private final ObjectProperty<Path> vaultPath;
|
||||||
private final StringProperty vaultName;
|
private final StringProperty vaultName;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final BooleanBinding validVaultPath;
|
private final BooleanBinding validVaultPath;
|
||||||
private final BooleanProperty usePresetPath;
|
private final BooleanProperty usePresetPath;
|
||||||
private final StringProperty warningText;
|
private final StringProperty statusText;
|
||||||
|
private final ObjectProperty<Node> statusGraphic;
|
||||||
|
|
||||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||||
|
|
||||||
|
//FXML
|
||||||
public ToggleGroup predefinedLocationToggler;
|
public ToggleGroup predefinedLocationToggler;
|
||||||
public RadioButton iclouddriveRadioButton;
|
public RadioButton iclouddriveRadioButton;
|
||||||
public RadioButton dropboxRadioButton;
|
public RadioButton dropboxRadioButton;
|
||||||
public RadioButton gdriveRadioButton;
|
public RadioButton gdriveRadioButton;
|
||||||
public RadioButton onedriveRadioButton;
|
public RadioButton onedriveRadioButton;
|
||||||
|
public RadioButton megaRadioButton;
|
||||||
|
public RadioButton pcloudRadioButton;
|
||||||
public RadioButton customRadioButton;
|
public RadioButton customRadioButton;
|
||||||
|
public Label vaultPathStatus;
|
||||||
|
public FontAwesome5IconView goodLocation;
|
||||||
|
public FontAwesome5IconView badLocation;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, ErrorComponent.Builder errorComponent, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.chooseNameScene = chooseNameScene;
|
this.chooseNameScene = chooseNameScene;
|
||||||
this.choosePasswordScene = choosePasswordScene;
|
this.choosePasswordScene = choosePasswordScene;
|
||||||
this.errorComponent = errorComponent;
|
|
||||||
this.locationPresets = locationPresets;
|
this.locationPresets = locationPresets;
|
||||||
this.vaultPath = vaultPath;
|
this.vaultPath = vaultPath;
|
||||||
this.vaultName = vaultName;
|
this.vaultName = vaultName;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.validVaultPath = Bindings.createBooleanBinding(this::isValidVaultPath, vaultPath);
|
this.validVaultPath = Bindings.createBooleanBinding(this::validateVaultPathAndSetStatus, this.vaultPath);
|
||||||
this.usePresetPath = new SimpleBooleanProperty();
|
this.usePresetPath = new SimpleBooleanProperty();
|
||||||
this.warningText = new SimpleStringProperty();
|
this.statusText = new SimpleStringProperty();
|
||||||
|
this.statusGraphic = new SimpleObjectProperty<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidVaultPath() {
|
private boolean validateVaultPathAndSetStatus() {
|
||||||
return vaultPath.get() != null && Files.notExists(vaultPath.get());
|
final Path p = vaultPath.get();
|
||||||
|
if (p == null) {
|
||||||
|
statusText.set("Error: Path is NULL.");
|
||||||
|
statusGraphic.set(badLocation);
|
||||||
|
return false;
|
||||||
|
} else if (!Files.exists(p.getParent())) {
|
||||||
|
statusText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
|
||||||
|
statusGraphic.set(badLocation);
|
||||||
|
return false;
|
||||||
|
} else if (!Files.isWritable(p.getParent())) {
|
||||||
|
statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsNotWritable"));
|
||||||
|
statusGraphic.set(badLocation);
|
||||||
|
return false;
|
||||||
|
} else if (!Files.notExists(p)) {
|
||||||
|
statusText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
||||||
|
statusGraphic.set(badLocation);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
statusText.set(resourceBundle.getString("addvaultwizard.new.locationIsOk"));
|
||||||
|
statusGraphic.set(goodLocation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||||
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
||||||
vaultPath.addListener(this::vaultPathDidChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
|
|
||||||
if (!Files.notExists(newValue)) {
|
|
||||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
|
||||||
} else {
|
|
||||||
warningText.set(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||||
@@ -103,6 +122,10 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
||||||
} else if (onedriveRadioButton.equals(newValue)) {
|
} else if (onedriveRadioButton.equals(newValue)) {
|
||||||
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
|
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
|
||||||
|
} else if (megaRadioButton.equals(newValue)) {
|
||||||
|
vaultPath.set(locationPresets.getMegaLocation().resolve(vaultName.get()));
|
||||||
|
} else if (pcloudRadioButton.equals(newValue)) {
|
||||||
|
vaultPath.set(locationPresets.getPcloudLocation().resolve(vaultName.get()));
|
||||||
} else if (customRadioButton.equals(newValue)) {
|
} else if (customRadioButton.equals(newValue)) {
|
||||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||||
}
|
}
|
||||||
@@ -115,21 +138,10 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
try {
|
if (validateVaultPathAndSetStatus()) {
|
||||||
// check if we have write access AND the vaultPath doesn't already exist:
|
|
||||||
assert Files.isDirectory(vaultPath.get().getParent());
|
|
||||||
Path createdDir = Files.createDirectory(vaultPath.get());
|
|
||||||
Files.delete(createdDir); // assert: dir exists and is empty
|
|
||||||
window.setScene(choosePasswordScene.get());
|
window.setScene(choosePasswordScene.get());
|
||||||
} catch (FileAlreadyExistsException e) {
|
} else {
|
||||||
LOG.warn("Can not use already existing vault path {}", vaultPath.get());
|
validVaultPath.invalidate();
|
||||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
|
||||||
} catch (NoSuchFileException e) {
|
|
||||||
LOG.warn("At least one path component does not exist of path {}", vaultPath.get());
|
|
||||||
warningText.set(resourceBundle.getString("addvaultwizard.new.locationDoesNotExist"));
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Failed to create and delete directory at chosen vault path.", e);
|
|
||||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,19 +191,27 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
return usePresetPath.get();
|
return usePresetPath.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty warningTextProperty() {
|
public BooleanBinding anyRadioButtonSelectedProperty() {
|
||||||
return warningText;
|
return predefinedLocationToggler.selectedToggleProperty().isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWarningText() {
|
public boolean isAnyRadioButtonSelected() {
|
||||||
return warningText.get();
|
return anyRadioButtonSelectedProperty().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding showWarningProperty() {
|
public StringProperty statusTextProperty() {
|
||||||
return warningText.isNotEmpty();
|
return statusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isShowWarning() {
|
public String getStatusText() {
|
||||||
return showWarningProperty().get();
|
return statusText.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Node> statusGraphicProperty() {
|
||||||
|
return statusGraphic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getStatusGraphic() {
|
||||||
|
return statusGraphic.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-13
@@ -5,11 +5,17 @@ import org.cryptomator.common.vaults.Vault;
|
|||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptofs.VaultCipherCombo;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.Tasks;
|
import org.cryptomator.ui.common.Tasks;
|
||||||
|
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -31,13 +37,14 @@ import javafx.scene.control.ToggleGroup;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Collections;
|
import java.security.SecureRandom;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
@@ -48,6 +55,7 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
|||||||
public class CreateNewVaultPasswordController implements FxController {
|
public class CreateNewVaultPasswordController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
|
||||||
|
private static final URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); // TODO better place?
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> chooseLocationScene;
|
private final Lazy<Scene> chooseLocationScene;
|
||||||
@@ -64,6 +72,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final ObjectProperty<CharSequence> password;
|
private final ObjectProperty<CharSequence> password;
|
||||||
private final ReadmeGenerator readmeGenerator;
|
private final ReadmeGenerator readmeGenerator;
|
||||||
|
private final SecureRandom csprng;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
private final BooleanProperty processing;
|
private final BooleanProperty processing;
|
||||||
private final BooleanProperty readyToCreateVault;
|
private final BooleanProperty readyToCreateVault;
|
||||||
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
||||||
@@ -73,7 +83,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
public Toggle skipRecoveryKey;
|
public Toggle skipRecoveryKey;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
|
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ErrorComponent.Builder errorComponent, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.chooseLocationScene = chooseLocationScene;
|
this.chooseLocationScene = chooseLocationScene;
|
||||||
this.recoveryKeyScene = recoveryKeyScene;
|
this.recoveryKeyScene = recoveryKeyScene;
|
||||||
@@ -89,6 +99,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.readmeGenerator = readmeGenerator;
|
this.readmeGenerator = readmeGenerator;
|
||||||
|
this.csprng = csprng;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
this.processing = new SimpleBooleanProperty();
|
this.processing = new SimpleBooleanProperty();
|
||||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||||
@@ -161,23 +173,34 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeVault(Path path, CharSequence passphrase) throws IOException {
|
private void initializeVault(Path path, CharSequence passphrase) throws IOException {
|
||||||
CryptoFileSystemProvider.initialize(path, MASTERKEY_FILENAME, passphrase);
|
// 1. write masterkey:
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
Path masterkeyFilePath = path.resolve(MASTERKEY_FILENAME);
|
||||||
.withPassphrase(passphrase) //
|
try (Masterkey masterkey = Masterkey.generate(csprng)) {
|
||||||
.withFlags(Collections.emptySet()) //
|
masterkeyFileAccess.persist(masterkey, masterkeyFilePath, passphrase);
|
||||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName");
|
// 2. initialize vault:
|
||||||
try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); //
|
try {
|
||||||
WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
MasterkeyLoader loader = ignored -> masterkey.clone();
|
||||||
ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf()));
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoader(loader).build();
|
||||||
|
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
|
||||||
|
|
||||||
|
// 3. write vault-internal readme file:
|
||||||
|
String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName");
|
||||||
|
try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); //
|
||||||
|
WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||||
|
ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf()));
|
||||||
|
}
|
||||||
|
} catch (CryptoException e) {
|
||||||
|
throw new IOException("Failed initialize vault.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. write vault-external readme file:
|
||||||
String storagePathReadmeFileName = resourceBundle.getString("addvault.new.readme.storageLocation.fileName");
|
String storagePathReadmeFileName = resourceBundle.getString("addvault.new.readme.storageLocation.fileName");
|
||||||
try (WritableByteChannel ch = Files.newByteChannel(path.resolve(storagePathReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
try (WritableByteChannel ch = Files.newByteChannel(path.resolve(storagePathReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||||
ch.write(US_ASCII.encode(readmeGenerator.createVaultStorageLocationReadmeRtf()));
|
ch.write(US_ASCII.encode(readmeGenerator.createVaultStorageLocationReadmeRtf()));
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("Created vault at {}", path);
|
LOG.info("Created vault at {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +208,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
try {
|
try {
|
||||||
Vault newVault = vaultListManager.add(pathToVault);
|
Vault newVault = vaultListManager.add(pathToVault);
|
||||||
vaultProperty.set(newVault);
|
vaultProperty.set(newVault);
|
||||||
} catch (NoSuchFileException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,21 @@ public class LocationPresets {
|
|||||||
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
||||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
||||||
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
|
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
|
||||||
|
private static final String[] MEGA_LOCATIONS = {"~/MEGA"};
|
||||||
|
private static final String[] PCLOUD_LOCATIONS = {"~/pCloudDrive"};
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
|
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> gdriveLocation;
|
private final ReadOnlyObjectProperty<Path> gdriveLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> onedriveLocation;
|
private final ReadOnlyObjectProperty<Path> onedriveLocation;
|
||||||
|
private final ReadOnlyObjectProperty<Path> megaLocation;
|
||||||
|
private final ReadOnlyObjectProperty<Path> pcloudLocation;
|
||||||
private final BooleanBinding foundIclouddrive;
|
private final BooleanBinding foundIclouddrive;
|
||||||
private final BooleanBinding foundDropbox;
|
private final BooleanBinding foundDropbox;
|
||||||
private final BooleanBinding foundGdrive;
|
private final BooleanBinding foundGdrive;
|
||||||
private final BooleanBinding foundOnedrive;
|
private final BooleanBinding foundOnedrive;
|
||||||
|
private final BooleanBinding foundMega;
|
||||||
|
private final BooleanBinding foundPcloud;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LocationPresets() {
|
public LocationPresets() {
|
||||||
@@ -32,10 +38,14 @@ public class LocationPresets {
|
|||||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
||||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
||||||
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
||||||
|
this.megaLocation = new SimpleObjectProperty<>(existingWritablePath(MEGA_LOCATIONS));
|
||||||
|
this.pcloudLocation = new SimpleObjectProperty<>(existingWritablePath(PCLOUD_LOCATIONS));
|
||||||
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
||||||
this.foundDropbox = dropboxLocation.isNotNull();
|
this.foundDropbox = dropboxLocation.isNotNull();
|
||||||
this.foundGdrive = gdriveLocation.isNotNull();
|
this.foundGdrive = gdriveLocation.isNotNull();
|
||||||
this.foundOnedrive = onedriveLocation.isNotNull();
|
this.foundOnedrive = onedriveLocation.isNotNull();
|
||||||
|
this.foundMega = megaLocation.isNotNull();
|
||||||
|
this.foundPcloud = pcloudLocation.isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path existingWritablePath(String... candidates) {
|
private static Path existingWritablePath(String... candidates) {
|
||||||
@@ -122,4 +132,36 @@ public class LocationPresets {
|
|||||||
return foundOnedrive.get();
|
return foundOnedrive.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<Path> megaLocationProperty() {
|
||||||
|
return megaLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getMegaLocation() {
|
||||||
|
return megaLocation.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding foundMegaProperty() {
|
||||||
|
return foundMega;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFoundMega() {
|
||||||
|
return foundMega.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<Path> pcloudLocationProperty() {
|
||||||
|
return pcloudLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getPcloudLocation() {
|
||||||
|
return pcloudLocation.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding foundPcloudProperty() {
|
||||||
|
return foundPcloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFoundPcloud() {
|
||||||
|
return foundPcloud.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-7
@@ -2,8 +2,10 @@ package org.cryptomator.ui.changepassword;
|
|||||||
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.common.MasterkeyBackupHelper;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
import org.cryptomator.ui.common.Animations;
|
import org.cryptomator.ui.common.Animations;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
@@ -23,8 +25,13 @@ import javafx.scene.control.CheckBox;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.util.Optional;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
|
||||||
@ChangePasswordScoped
|
@ChangePasswordScoped
|
||||||
@@ -37,18 +44,22 @@ public class ChangePasswordController implements FxController {
|
|||||||
private final ObjectProperty<CharSequence> newPassword;
|
private final ObjectProperty<CharSequence> newPassword;
|
||||||
private final ErrorComponent.Builder errorComponent;
|
private final ErrorComponent.Builder errorComponent;
|
||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
|
private final SecureRandom csprng;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
|
|
||||||
public NiceSecurePasswordField oldPasswordField;
|
public NiceSecurePasswordField oldPasswordField;
|
||||||
public CheckBox finalConfirmationCheckbox;
|
public CheckBox finalConfirmationCheckbox;
|
||||||
public Button finishButton;
|
public Button finishButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain) {
|
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, KeychainManager keychain, SecureRandom csprng, MasterkeyFileAccess masterkeyFileAccess) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.newPassword = newPassword;
|
this.newPassword = newPassword;
|
||||||
this.errorComponent = errorComponent;
|
this.errorComponent = errorComponent;
|
||||||
this.keychain = keychain;
|
this.keychain = keychain;
|
||||||
|
this.csprng = csprng;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -67,17 +78,26 @@ public class ChangePasswordController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void finish() {
|
public void finish() {
|
||||||
try {
|
try {
|
||||||
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
|
//String normalizedOldPassphrase = Normalizer.normalize(oldPasswordField.getCharacters(), Normalizer.Form.NFC);
|
||||||
|
//String normalizedNewPassphrase = Normalizer.normalize(newPassword.get(), Normalizer.Form.NFC);
|
||||||
|
CharSequence oldPassphrase = oldPasswordField.getCharacters(); // TODO verify: is this already NFC-normalized?
|
||||||
|
CharSequence newPassphrase = newPassword.get(); // TODO verify: is this already NFC-normalized?
|
||||||
|
Path masterkeyPath = vault.getPath().resolve(MASTERKEY_FILENAME);
|
||||||
|
byte[] oldMasterkeyBytes = Files.readAllBytes(masterkeyPath);
|
||||||
|
byte[] newMasterkeyBytes = masterkeyFileAccess.changePassphrase(oldMasterkeyBytes, oldPassphrase, newPassphrase);
|
||||||
|
Path backupKeyPath = vault.getPath().resolve(MASTERKEY_FILENAME + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + MASTERKEY_BACKUP_SUFFIX);
|
||||||
|
Files.move(masterkeyPath, backupKeyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
Files.write(masterkeyPath, newMasterkeyBytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
|
||||||
LOG.info("Successfully changed password for {}", vault.getDisplayName());
|
LOG.info("Successfully changed password for {}", vault.getDisplayName());
|
||||||
window.close();
|
window.close();
|
||||||
updatePasswordInSystemkeychain();
|
updatePasswordInSystemkeychain();
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
|
||||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
|
||||||
} catch (InvalidPassphraseException e) {
|
} catch (InvalidPassphraseException e) {
|
||||||
Animations.createShakeWindowAnimation(window).play();
|
Animations.createShakeWindowAnimation(window).play();
|
||||||
oldPasswordField.selectAll();
|
oldPasswordField.selectAll();
|
||||||
oldPasswordField.requestFocus();
|
oldPasswordField.requestFocus();
|
||||||
|
} catch (IOException | CryptoException e) {
|
||||||
|
LOG.error("Password change failed. Unable to perform operation.", e);
|
||||||
|
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -37,8 +37,8 @@ abstract class ChangePasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@ChangePasswordWindow
|
@ChangePasswordWindow
|
||||||
@ChangePasswordScoped
|
@ChangePasswordScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -56,8 +56,8 @@ abstract class ChangePasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
||||||
@ChangePasswordScoped
|
@ChangePasswordScoped
|
||||||
static Scene provideUnlockScene(@ChangePasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideUnlockScene(@ChangePasswordWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/changepassword.fxml");
|
return fxmlLoaders.createScene(FxmlFile.CHANGEPASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ public class DefaultSceneFactory implements Function<Parent, Scene> {
|
|||||||
protected void configureScene(Scene scene) {
|
protected void configureScene(Scene scene) {
|
||||||
scene.windowProperty().addListener(observable -> {
|
scene.windowProperty().addListener(observable -> {
|
||||||
Window window = scene.getWindow();
|
Window window = scene.getWindow();
|
||||||
if (window instanceof Stage) {
|
if (window instanceof Stage s) {
|
||||||
setupDefaultAccelerators(scene, (Stage) window);
|
setupDefaultAccelerators(scene, s);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import java.util.ResourceBundle;
|
|||||||
abstract class ErrorModule {
|
abstract class ErrorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -38,8 +38,8 @@ abstract class ErrorModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.ERROR)
|
@FxmlScene(FxmlFile.ERROR)
|
||||||
static Scene provideErrorScene(FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideErrorScene(FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene(FxmlFile.ERROR.getRessourcePathString());
|
return fxmlLoaders.createScene(FxmlFile.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ public enum FxmlFile {
|
|||||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||||
UNLOCK("/fxml/unlock.fxml"),
|
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
|
||||||
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
|
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
|
||||||
|
UNLOCK_SELECT_MASTERKEYFILE("/fxml/unlock_select_masterkeyfile.fxml"), //
|
||||||
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
||||||
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
||||||
VAULT_STATISTICS("/fxml/stats.fxml"), //
|
VAULT_STATISTICS("/fxml/stats.fxml"), //
|
||||||
@@ -39,7 +40,7 @@ public enum FxmlFile {
|
|||||||
this.ressourcePathString = ressourcePathString;
|
this.ressourcePathString = ressourcePathString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRessourcePathString() {
|
String getRessourcePathString() {
|
||||||
return ressourcePathString;
|
return ressourcePathString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -11,13 +11,13 @@ import java.util.Map;
|
|||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class FXMLLoaderFactory {
|
public class FxmlLoaderFactory {
|
||||||
|
|
||||||
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
||||||
private final Function<Parent, Scene> sceneFactory;
|
private final Function<Parent, Scene> sceneFactory;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
public FXMLLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
public FxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
||||||
this.controllerFactories = controllerFactories;
|
this.controllerFactories = controllerFactories;
|
||||||
this.sceneFactory = sceneFactory;
|
this.sceneFactory = sceneFactory;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
@@ -48,13 +48,17 @@ public class FXMLLoaderFactory {
|
|||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Scene createScene(FxmlFile fxmlFile) {
|
||||||
|
return createScene(fxmlFile.getRessourcePathString());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
||||||
*
|
*
|
||||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||||
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
||||||
*/
|
*/
|
||||||
public Scene createScene(String fxmlResourceName) {
|
private Scene createScene(String fxmlResourceName) {
|
||||||
final FXMLLoader loader;
|
final FXMLLoader loader;
|
||||||
try {
|
try {
|
||||||
loader = load(fxmlResourceName);
|
loader = load(fxmlResourceName);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.common;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.LockNotCompletedException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.common.vaults.Volume;
|
||||||
@@ -175,24 +176,29 @@ public class VaultService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Vault call() throws Volume.VolumeException {
|
protected Vault call() throws Volume.VolumeException, LockNotCompletedException {
|
||||||
vault.lock(forced);
|
vault.lock(forced);
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void scheduled() {
|
protected void scheduled() {
|
||||||
vault.setState(VaultState.PROCESSING);
|
vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
vault.setState(VaultState.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failed() {
|
protected void failed() {
|
||||||
vault.setState(VaultState.UNLOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cancelled() {
|
||||||
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -29,8 +29,8 @@ abstract class ForgetPasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@ForgetPasswordWindow
|
@ForgetPasswordWindow
|
||||||
@ForgetPasswordScoped
|
@ForgetPasswordScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -62,8 +62,8 @@ abstract class ForgetPasswordModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
@FxmlScene(FxmlFile.FORGET_PASSWORD)
|
||||||
@ForgetPasswordScoped
|
@ForgetPasswordScoped
|
||||||
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideForgetPasswordScene(@ForgetPasswordWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/forget_password.fxml");
|
return fxmlLoaders.createScene(FxmlFile.FORGET_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import org.cryptomator.common.LicenseHolder;
|
|||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||||
import org.cryptomator.integrations.uiappearance.Theme;
|
import org.cryptomator.integrations.uiappearance.Theme;
|
||||||
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
|
import org.cryptomator.integrations.uiappearance.UiAppearanceException;
|
||||||
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
|
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
|
||||||
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
|
||||||
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.cryptomator.ui.lock.LockComponent;
|
import org.cryptomator.ui.lock.LockComponent;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
@@ -44,8 +46,9 @@ public class FxApplication extends Application {
|
|||||||
private final Lazy<MainWindowComponent> mainWindow;
|
private final Lazy<MainWindowComponent> mainWindow;
|
||||||
private final Lazy<PreferencesComponent> preferencesWindow;
|
private final Lazy<PreferencesComponent> preferencesWindow;
|
||||||
private final Lazy<QuitComponent> quitWindow;
|
private final Lazy<QuitComponent> quitWindow;
|
||||||
private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
|
private final Provider<UnlockComponent.Builder> unlockWorkflowBuilderProvider;
|
||||||
private final Provider<LockComponent.Builder> lockWindowBuilderProvider;
|
private final Provider<LockComponent.Builder> lockWorkflowBuilderProvider;
|
||||||
|
private final ErrorComponent.Builder errorWindowBuilder;
|
||||||
private final Optional<TrayIntegrationProvider> trayIntegration;
|
private final Optional<TrayIntegrationProvider> trayIntegration;
|
||||||
private final Optional<UiAppearanceProvider> appearanceProvider;
|
private final Optional<UiAppearanceProvider> appearanceProvider;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
@@ -55,13 +58,14 @@ public class FxApplication extends Application {
|
|||||||
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<LockComponent.Builder> lockWindowBuilderProvider, Lazy<QuitComponent> quitWindow, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
|
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWorkflowBuilderProvider, Provider<LockComponent.Builder> lockWorkflowBuilderProvider, Lazy<QuitComponent> quitWindow, ErrorComponent.Builder errorWindowBuilder, Optional<TrayIntegrationProvider> trayIntegration, Optional<UiAppearanceProvider> appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.preferencesWindow = preferencesWindow;
|
this.preferencesWindow = preferencesWindow;
|
||||||
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
|
this.unlockWorkflowBuilderProvider = unlockWorkflowBuilderProvider;
|
||||||
this.lockWindowBuilderProvider = lockWindowBuilderProvider;
|
this.lockWorkflowBuilderProvider = lockWorkflowBuilderProvider;
|
||||||
this.quitWindow = quitWindow;
|
this.quitWindow = quitWindow;
|
||||||
|
this.errorWindowBuilder = errorWindowBuilder;
|
||||||
this.trayIntegration = trayIntegration;
|
this.trayIntegration = trayIntegration;
|
||||||
this.appearanceProvider = appearanceProvider;
|
this.appearanceProvider = appearanceProvider;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
@@ -86,7 +90,7 @@ public class FxApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
|
private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
|
||||||
LOG.warn("has visible stages: {}", newValue);
|
LOG.debug("has visible stages: {}", newValue);
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
|
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
|
||||||
} else {
|
} else {
|
||||||
@@ -113,15 +117,23 @@ public class FxApplication extends Application {
|
|||||||
|
|
||||||
public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
|
public void startUnlockWorkflow(Vault vault, Optional<Stage> owner) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
unlockWindowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
|
if (vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING)) {
|
||||||
LOG.debug("Showing UnlockWindow for {}", vault.getDisplayName());
|
unlockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startUnlockWorkflow();
|
||||||
|
LOG.debug("Start unlock workflow for {}", vault.getDisplayName());
|
||||||
|
} else {
|
||||||
|
showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to unlock vault in non-locked state.")));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startLockWorkflow(Vault vault, Optional<Stage> owner) {
|
public void startLockWorkflow(Vault vault, Optional<Stage> owner) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
lockWindowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
|
if (vault.stateProperty().transition(VaultState.Value.UNLOCKED, VaultState.Value.PROCESSING)) {
|
||||||
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
|
lockWorkflowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
|
||||||
|
LOG.debug("Start lock workflow for {}", vault.getDisplayName());
|
||||||
|
} else {
|
||||||
|
showMainWindow().thenAccept(mainWindow -> errorWindowBuilder.window(mainWindow).cause(new IllegalStateException("Unable to lock vault in non-unlocked state.")));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,13 +149,13 @@ public class FxApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
private void appThemeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
||||||
appearanceProvider.ifPresent(appearanceProvider -> {
|
if (appearanceProvider.isPresent() && oldValue == UiTheme.AUTOMATIC && newValue != UiTheme.AUTOMATIC) {
|
||||||
try {
|
try {
|
||||||
appearanceProvider.removeListener(systemInterfaceThemeListener);
|
appearanceProvider.get().removeListener(systemInterfaceThemeListener);
|
||||||
} catch (UiAppearanceException e) {
|
} catch (UiAppearanceException e) {
|
||||||
LOG.error("Failed to disable automatic theme switching.");
|
LOG.error("Failed to disable automatic theme switching.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
loadSelectedStyleSheet(newValue);
|
loadSelectedStyleSheet(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
@@ -39,8 +40,13 @@ public abstract class UpdateCheckerModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
static HttpClient provideHttpClient() {
|
static Optional<HttpClient> provideHttpClient() {
|
||||||
return HttpClient.newHttpClient();
|
try {
|
||||||
|
return Optional.of(HttpClient.newHttpClient());
|
||||||
|
} catch (UncheckedIOException e) {
|
||||||
|
LOG.error("HttpClient for update check cannot be created.", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -66,11 +72,20 @@ public abstract class UpdateCheckerModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxApplicationScoped
|
@FxApplicationScoped
|
||||||
static ScheduledService<String> provideCheckForUpdatesService(ExecutorService executor, HttpClient httpClient, HttpRequest checkForUpdatesRequest, @Named("checkForUpdatesInterval") ObjectBinding<Duration> period) {
|
static ScheduledService<String> provideCheckForUpdatesService(ExecutorService executor, Optional<HttpClient> httpClient, HttpRequest checkForUpdatesRequest, @Named("checkForUpdatesInterval") ObjectBinding<Duration> period) {
|
||||||
ScheduledService<String> service = new ScheduledService<>() {
|
ScheduledService<String> service = new ScheduledService<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Task<String> createTask() {
|
protected Task<String> createTask() {
|
||||||
return new UpdateCheckerTask(httpClient, checkForUpdatesRequest);
|
if (httpClient.isPresent()) {
|
||||||
|
return new UpdateCheckerTask(httpClient.get(), checkForUpdatesRequest);
|
||||||
|
} else {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected String call() {
|
||||||
|
throw new NullPointerException("No HttpClient present.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
service.setOnFailed(event -> LOG.error("Failed to execute update service", service.getException()));
|
service.setOnFailed(event -> LOG.error("Failed to execute update service", service.getException()));
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.ui.keyloading;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface KeyLoading {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.cryptomator.ui.keyloading;
|
||||||
|
|
||||||
|
import dagger.BindsInstance;
|
||||||
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
@Subcomponent(modules = {KeyLoadingModule.class})
|
||||||
|
public interface KeyLoadingComponent {
|
||||||
|
|
||||||
|
@KeyLoading
|
||||||
|
KeyLoadingStrategy keyloadingStrategy();
|
||||||
|
|
||||||
|
@Subcomponent.Builder
|
||||||
|
interface Builder {
|
||||||
|
|
||||||
|
@BindsInstance
|
||||||
|
Builder vault(@KeyLoading Vault vault);
|
||||||
|
|
||||||
|
@BindsInstance
|
||||||
|
Builder window(@KeyLoading Stage window);
|
||||||
|
|
||||||
|
KeyLoadingComponent build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.cryptomator.ui.keyloading;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||||
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
|
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
|
||||||
|
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
@Module(includes = {MasterkeyFileLoadingModule.class})
|
||||||
|
abstract class KeyLoadingModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoading
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoading
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Optional<URI> provideKeyId(@KeyLoading Vault vault) {
|
||||||
|
return vault.getUnverifiedVaultConfig().map(UnverifiedVaultConfig::getKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoading
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Optional<URI> keyId, Map<String, Provider<KeyLoadingStrategy>> strategies) {
|
||||||
|
if (keyId.isEmpty()) {
|
||||||
|
return KeyLoadingStrategy.failed(new IllegalArgumentException("No key id provided"));
|
||||||
|
} else {
|
||||||
|
String scheme = keyId.get().getScheme();
|
||||||
|
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
|
||||||
|
return strategies.getOrDefault(scheme, () -> fallback).get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.cryptomator.ui.keyloading;
|
||||||
|
|
||||||
|
import javax.inject.Scope;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Scope
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface KeyLoadingScoped {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.cryptomator.ui.keyloading;
|
||||||
|
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable, stateful {@link MasterkeyLoader}, that can deal with certain exceptions.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface KeyLoadingStrategy extends MasterkeyLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a master key. This might be a long-running operation, as it may require user input or expensive computations.
|
||||||
|
* <p>
|
||||||
|
* If loading fails exceptionally, this strategy might be able to {@link #recoverFromException(MasterkeyLoadingFailedException) recover from this exception}, so it can be used in a further attempt.
|
||||||
|
*
|
||||||
|
* @param keyId An URI uniquely identifying the source and identity of the key
|
||||||
|
* @return The raw key bytes. Must not be null
|
||||||
|
* @throws MasterkeyLoadingFailedException Thrown when it is impossible to fulfill the request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the loader to try and recover from an exception thrown during the last attempt.
|
||||||
|
*
|
||||||
|
* @param exception An exception thrown by {@link #loadKey(URI)}.
|
||||||
|
* @return <code>true</code> if this component was able to handle the exception and another attempt can be made to load a masterkey
|
||||||
|
*/
|
||||||
|
default boolean recoverFromException(MasterkeyLoadingFailedException exception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release any ressources or do follow-up tasks after loading a key.
|
||||||
|
*
|
||||||
|
* @param unlockedSuccessfully <code>true</code> if successfully unlocked a vault with the loaded key
|
||||||
|
* @implNote This method might be invoked multiple times, depending on whether multiple attempts to load a key are started.
|
||||||
|
*/
|
||||||
|
default void cleanup(boolean unlockedSuccessfully) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key loading strategy that will always fail by throwing a {@link MasterkeyLoadingFailedException}.
|
||||||
|
*
|
||||||
|
* @param exception The cause of the failure. If not alreay an {@link MasterkeyLoadingFailedException}, it will get wrapped.
|
||||||
|
* @return A new KeyLoadingStrategy that will always fail with an {@link MasterkeyLoadingFailedException}.
|
||||||
|
*/
|
||||||
|
static KeyLoadingStrategy failed(Exception exception) {
|
||||||
|
return keyid -> {
|
||||||
|
if (exception instanceof MasterkeyLoadingFailedException e) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
throw new MasterkeyLoadingFailedException("Can not load key", exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
class MasterkeyFileLoadingFinisher {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class);
|
||||||
|
|
||||||
|
private final Vault vault;
|
||||||
|
private final Optional<char[]> storedPassword;
|
||||||
|
private final AtomicReference<char[]> enteredPassword;
|
||||||
|
private final AtomicBoolean shouldSavePassword;
|
||||||
|
private final KeychainManager keychain;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional<char[]> storedPassword, AtomicReference<char[]> enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) {
|
||||||
|
this.vault = vault;
|
||||||
|
this.storedPassword = storedPassword;
|
||||||
|
this.enteredPassword = enteredPassword;
|
||||||
|
this.shouldSavePassword = shouldSavePassword;
|
||||||
|
this.keychain = keychain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanup(boolean successfullyUnlocked) {
|
||||||
|
if (successfullyUnlocked && shouldSavePassword.get()) {
|
||||||
|
savePasswordToSystemkeychain();
|
||||||
|
}
|
||||||
|
wipePassword(storedPassword.orElse(null));
|
||||||
|
wipePassword(enteredPassword.getAndSet(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePasswordToSystemkeychain() {
|
||||||
|
if (keychain.isSupported()) {
|
||||||
|
try {
|
||||||
|
keychain.storePassphrase(vault.getId(), CharBuffer.wrap(enteredPassword.get()));
|
||||||
|
} catch (KeychainAccessException e) {
|
||||||
|
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wipePassword(char[] pw) {
|
||||||
|
if (pw != null) {
|
||||||
|
Arrays.fill(pw, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
import dagger.multibindings.StringKey;
|
||||||
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@Module(subcomponents = {ForgetPasswordComponent.class})
|
||||||
|
public abstract class MasterkeyFileLoadingModule {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class);
|
||||||
|
|
||||||
|
public enum PasswordEntry {
|
||||||
|
PASSWORD_ENTERED,
|
||||||
|
CANCELED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MasterkeyFileProvision {
|
||||||
|
MASTERKEYFILE_PROVIDED,
|
||||||
|
CANCELED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
|
||||||
|
return new UserInteractionLock<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static UserInteractionLock<MasterkeyFileProvision> provideMasterkeyFileProvisionLock() {
|
||||||
|
return new UserInteractionLock<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("savedPassword")
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading Vault vault) {
|
||||||
|
if (!keychain.isSupported()) {
|
||||||
|
return Optional.empty();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
|
||||||
|
} catch (KeychainAccessException e) {
|
||||||
|
LOG.error("Failed to load entry from system keychain.", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static AtomicReference<Path> provideUserProvidedMasterkeyPath() {
|
||||||
|
return new AtomicReference<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
|
||||||
|
return new AtomicReference<>(storedPassword.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("savePassword")
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
|
||||||
|
return new AtomicBoolean(storedPassword.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE)
|
||||||
|
@KeyLoadingScoped
|
||||||
|
static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(PassphraseEntryController.class)
|
||||||
|
abstract FxController bindUnlockController(PassphraseEntryController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(SelectMasterkeyFileController.class)
|
||||||
|
abstract FxController bindUnlockSelectMasterkeyFileController(SelectMasterkeyFileController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@KeyLoadingScoped
|
||||||
|
@StringKey(MasterkeyFileLoadingStrategy.SCHEME)
|
||||||
|
abstract KeyLoadingStrategy bindMasterkeyFileLoadingStrategy(MasterkeyFileLoadingStrategy strategy);
|
||||||
|
|
||||||
|
}
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
import org.cryptomator.ui.common.Animations;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
|
import org.cryptomator.ui.unlock.UnlockCancelledException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@KeyLoading
|
||||||
|
public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||||
|
|
||||||
|
public static final String SCHEME = "masterkeyfile";
|
||||||
|
|
||||||
|
private final Vault vault;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAcccess;
|
||||||
|
private final Stage window;
|
||||||
|
private final Lazy<Scene> passphraseEntryScene;
|
||||||
|
private final Lazy<Scene> selectMasterkeyFileScene;
|
||||||
|
private final UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock;
|
||||||
|
private final UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock;
|
||||||
|
private final AtomicReference<char[]> password;
|
||||||
|
private final AtomicReference<Path> filePath;
|
||||||
|
private final MasterkeyFileLoadingFinisher finisher;
|
||||||
|
|
||||||
|
private boolean wrongPassword;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAcccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy<Scene> passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<char[]> password, AtomicReference<Path> filePath, MasterkeyFileLoadingFinisher finisher) {
|
||||||
|
this.vault = vault;
|
||||||
|
this.masterkeyFileAcccess = masterkeyFileAcccess;
|
||||||
|
this.window = window;
|
||||||
|
this.passphraseEntryScene = passphraseEntryScene;
|
||||||
|
this.selectMasterkeyFileScene = selectMasterkeyFileScene;
|
||||||
|
this.passwordEntryLock = passwordEntryLock;
|
||||||
|
this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
|
||||||
|
this.password = password;
|
||||||
|
this.filePath = filePath;
|
||||||
|
this.finisher = finisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
||||||
|
Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart());
|
||||||
|
if (!Files.exists(filePath)) {
|
||||||
|
filePath = getAlternateMasterkeyFilePath();
|
||||||
|
}
|
||||||
|
CharSequence passphrase = getPassphrase();
|
||||||
|
return masterkeyFileAcccess.load(filePath, passphrase);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new UnlockCancelledException("Unlock interrupted", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean recoverFromException(MasterkeyLoadingFailedException exception) {
|
||||||
|
if (exception instanceof InvalidPassphraseException) {
|
||||||
|
this.wrongPassword = true;
|
||||||
|
password.set(null);
|
||||||
|
return true; // reattempting key load
|
||||||
|
} else {
|
||||||
|
return false; // nothing we can do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup(boolean unlockedSuccessfully) {
|
||||||
|
finisher.cleanup(unlockedSuccessfully);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException {
|
||||||
|
if (filePath == null) {
|
||||||
|
return switch (askUserForMasterkeyFilePath()) {
|
||||||
|
case MASTERKEYFILE_PROVIDED -> filePath.get();
|
||||||
|
case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return filePath.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
window.setScene(selectMasterkeyFileScene.get());
|
||||||
|
window.show();
|
||||||
|
Window owner = window.getOwner();
|
||||||
|
if (owner != null) {
|
||||||
|
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
|
||||||
|
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
|
||||||
|
} else {
|
||||||
|
window.centerOnScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return masterkeyFileProvisionLock.awaitInteraction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException {
|
||||||
|
if (password.get() == null) {
|
||||||
|
return switch (askForPassphrase()) {
|
||||||
|
case PASSWORD_ENTERED -> CharBuffer.wrap(password.get());
|
||||||
|
case CANCELED -> throw new UnlockCancelledException("Password entry cancelled.");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// e.g. pre-filled from keychain or previous unlock attempt
|
||||||
|
return CharBuffer.wrap(password.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
window.setScene(passphraseEntryScene.get());
|
||||||
|
window.show();
|
||||||
|
Window owner = window.getOwner();
|
||||||
|
if (owner != null) {
|
||||||
|
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
|
||||||
|
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
|
||||||
|
} else {
|
||||||
|
window.centerOnScreen();
|
||||||
|
}
|
||||||
|
if (wrongPassword) {
|
||||||
|
Animations.createShakeWindowAnimation(window).play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return passwordEntryLock.awaitInteraction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+11
-8
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
@@ -7,6 +7,9 @@ import org.cryptomator.ui.common.UserInteractionLock;
|
|||||||
import org.cryptomator.ui.common.WeakBindings;
|
import org.cryptomator.ui.common.WeakBindings;
|
||||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -38,17 +41,17 @@ import java.util.Optional;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@UnlockScoped
|
@KeyLoadingScoped
|
||||||
public class UnlockController implements FxController {
|
public class PassphraseEntryController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final AtomicReference<char[]> password;
|
private final AtomicReference<char[]> password;
|
||||||
private final AtomicBoolean savePassword;
|
private final AtomicBoolean savePassword;
|
||||||
private final Optional<char[]> savedPassword;
|
private final Optional<char[]> savedPassword;
|
||||||
private final UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock;
|
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
|
||||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||||
private final KeychainManager keychain;
|
private final KeychainManager keychain;
|
||||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
|
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
|
||||||
@@ -66,7 +69,7 @@ public class UnlockController implements FxController {
|
|||||||
public Animation unlockAnimation;
|
public Animation unlockAnimation;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
|
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
@@ -138,7 +141,7 @@ public class UnlockController implements FxController {
|
|||||||
// if not already interacted, mark this workflow as cancelled:
|
// if not already interacted, mark this workflow as cancelled:
|
||||||
if (passwordEntryLock.awaitingInteraction().get()) {
|
if (passwordEntryLock.awaitingInteraction().get()) {
|
||||||
LOG.debug("Unlock canceled by user.");
|
LOG.debug("Unlock canceled by user.");
|
||||||
passwordEntryLock.interacted(UnlockModule.PasswordEntry.CANCELED);
|
passwordEntryLock.interacted(PasswordEntry.CANCELED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +157,7 @@ public class UnlockController implements FxController {
|
|||||||
if (oldPw != null) {
|
if (oldPw != null) {
|
||||||
Arrays.fill(oldPw, ' ');
|
Arrays.fill(oldPw, ' ');
|
||||||
}
|
}
|
||||||
passwordEntryLock.interacted(UnlockModule.PasswordEntry.PASSWORD_ENTERED);
|
passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED);
|
||||||
startUnlockAnimation();
|
startUnlockAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||||
|
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.MasterkeyFileProvision;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@KeyLoadingScoped
|
||||||
|
public class SelectMasterkeyFileController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class);
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final AtomicReference<Path> masterkeyPath;
|
||||||
|
private final UserInteractionLock<MasterkeyFileProvision> masterkeyFileProvisionLock;
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SelectMasterkeyFileController(@KeyLoading Stage window, AtomicReference<Path> masterkeyPath, UserInteractionLock<MasterkeyFileProvision> masterkeyFileProvisionLock, ResourceBundle resourceBundle) {
|
||||||
|
this.window = window;
|
||||||
|
this.masterkeyPath = masterkeyPath;
|
||||||
|
this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.window.setOnHiding(this::windowClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void cancel() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowClosed(WindowEvent windowEvent) {
|
||||||
|
// if not already interacted, mark this workflow as cancelled:
|
||||||
|
if (masterkeyFileProvisionLock.awaitingInteraction().get()) {
|
||||||
|
LOG.debug("Unlock canceled by user.");
|
||||||
|
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void proceed() {
|
||||||
|
LOG.trace("proceed()");
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle"));
|
||||||
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||||
|
File masterkeyFile = fileChooser.showOpenDialog(window);
|
||||||
|
if (masterkeyFile != null) {
|
||||||
|
LOG.debug("Chose masterkey file: {}", masterkeyFile);
|
||||||
|
masterkeyPath.set(masterkeyFile.toPath());
|
||||||
|
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.MASTERKEYFILE_PROVIDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.cryptomator.ui.launcher;
|
package org.cryptomator.ui.launcher;
|
||||||
|
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -9,6 +10,7 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
@@ -55,7 +57,7 @@ class AppLaunchEventHandler {
|
|||||||
case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
|
case REVEAL_APP -> fxApplicationStarter.get().thenAccept(FxApplication::showMainWindow);
|
||||||
case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
|
case OPEN_FILE -> fxApplicationStarter.get().thenRun(() -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
event.getPathsToOpen().forEach(this::addVault);
|
event.getPathsToOpen().forEach(this::addOrRevealVault);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
default -> LOG.warn("Unsupported event type: {}", event.getType());
|
default -> LOG.warn("Unsupported event type: {}", event.getType());
|
||||||
@@ -63,16 +65,21 @@ class AppLaunchEventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO dedup MainWindowController...
|
// TODO dedup MainWindowController...
|
||||||
private void addVault(Path potentialVaultPath) {
|
private void addOrRevealVault(Path potentialVaultPath) {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
try {
|
try {
|
||||||
|
final Vault v;
|
||||||
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
if (potentialVaultPath.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||||
vaultListManager.add(potentialVaultPath.getParent());
|
v = vaultListManager.add(potentialVaultPath.getParent());
|
||||||
} else {
|
} else {
|
||||||
vaultListManager.add(potentialVaultPath);
|
v = vaultListManager.add(potentialVaultPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.isUnlocked()) {
|
||||||
|
fxApplicationStarter.get().thenAccept(app -> app.getVaultService().reveal(v));
|
||||||
}
|
}
|
||||||
LOG.debug("Added vault {}", potentialVaultPath);
|
LOG.debug("Added vault {}", potentialVaultPath);
|
||||||
} catch (NoSuchFileException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to add vault " + potentialVaultPath, e);
|
LOG.error("Failed to add vault " + potentialVaultPath, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.cryptomator.ui.launcher;
|
package org.cryptomator.ui.launcher;
|
||||||
|
|
||||||
import org.cryptomator.common.ShutdownHook;
|
import org.cryptomator.common.ShutdownHook;
|
||||||
|
import org.cryptomator.common.vaults.LockNotCompletedException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.common.vaults.Volume;
|
||||||
@@ -24,11 +25,13 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AppLifecycleListener {
|
public class AppLifecycleListener {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AppLifecycleListener.class);
|
||||||
public static final Set<VaultState> STATES_ALLOWING_TERMINATION = EnumSet.of(VaultState.LOCKED, VaultState.NEEDS_MIGRATION, VaultState.MISSING, VaultState.ERROR);
|
public static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
|
||||||
|
|
||||||
private final FxApplicationStarter fxApplicationStarter;
|
private final FxApplicationStarter fxApplicationStarter;
|
||||||
private final CountDownLatch shutdownLatch;
|
private final CountDownLatch shutdownLatch;
|
||||||
@@ -127,6 +130,8 @@ public class AppLifecycleListener {
|
|||||||
vault.lock(true);
|
vault.lock(true);
|
||||||
} catch (Volume.VolumeException e) {
|
} catch (Volume.VolumeException e) {
|
||||||
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
||||||
|
} catch (LockNotCompletedException e) {
|
||||||
|
LOG.error("Failed to lock vault " + vault.getPath(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -40,8 +40,8 @@ abstract class LockModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@LockWindow
|
@LockWindow
|
||||||
@LockScoped
|
@LockScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -63,15 +63,15 @@ abstract class LockModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.LOCK_FORCED)
|
@FxmlScene(FxmlFile.LOCK_FORCED)
|
||||||
@LockScoped
|
@LockScoped
|
||||||
static Scene provideForceLockScene(@LockWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideForceLockScene(@LockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/lock_forced.fxml");
|
return fxmlLoaders.createScene(FxmlFile.LOCK_FORCED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.LOCK_FAILED)
|
@FxmlScene(FxmlFile.LOCK_FAILED)
|
||||||
@LockScoped
|
@LockScoped
|
||||||
static Scene provideLockFailedScene(@LockWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideLockFailedScene(@LockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/lock_failed.fxml");
|
return fxmlLoaders.createScene(FxmlFile.LOCK_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.cryptomator.ui.lock;
|
package org.cryptomator.ui.lock;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
import org.cryptomator.common.vaults.LockNotCompletedException;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume;
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.UserInteractionLock;
|
import org.cryptomator.ui.common.UserInteractionLock;
|
||||||
@@ -35,21 +37,23 @@ public class LockWorkflow extends Task<Void> {
|
|||||||
private final UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock;
|
private final UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock;
|
||||||
private final Lazy<Scene> lockForcedScene;
|
private final Lazy<Scene> lockForcedScene;
|
||||||
private final Lazy<Scene> lockFailedScene;
|
private final Lazy<Scene> lockFailedScene;
|
||||||
|
private final ErrorComponent.Builder errorComponent;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene) {
|
public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock<LockModule.ForceLockDecision> forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy<Scene> lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy<Scene> lockFailedScene, ErrorComponent.Builder errorComponent) {
|
||||||
this.lockWindow = lockWindow;
|
this.lockWindow = lockWindow;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.forceLockDecisionLock = forceLockDecisionLock;
|
this.forceLockDecisionLock = forceLockDecisionLock;
|
||||||
this.lockForcedScene = lockForcedScene;
|
this.lockForcedScene = lockForcedScene;
|
||||||
this.lockFailedScene = lockFailedScene;
|
this.lockFailedScene = lockFailedScene;
|
||||||
|
this.errorComponent = errorComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void call() throws Volume.VolumeException, InterruptedException {
|
protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException {
|
||||||
try {
|
try {
|
||||||
vault.lock(false);
|
vault.lock(false);
|
||||||
} catch (Volume.VolumeException e) {
|
} catch (Volume.VolumeException | LockNotCompletedException e) {
|
||||||
LOG.debug("Regular lock of {} failed.", vault.getDisplayName(), e);
|
LOG.debug("Regular lock of {} failed.", vault.getDisplayName(), e);
|
||||||
var decision = askUserForAction();
|
var decision = askUserForAction();
|
||||||
switch (decision) {
|
switch (decision) {
|
||||||
@@ -77,29 +81,29 @@ public class LockWorkflow extends Task<Void> {
|
|||||||
return forceLockDecisionLock.awaitInteraction();
|
return forceLockDecisionLock.awaitInteraction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void scheduled() {
|
|
||||||
vault.setState(VaultState.PROCESSING);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
|
LOG.info("Lock of {} succeeded.", vault.getDisplayName());
|
||||||
vault.setState(VaultState.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failed() {
|
protected void failed() {
|
||||||
LOG.warn("Failed to lock {}.", vault.getDisplayName());
|
final var throwable = super.getException();
|
||||||
vault.setState(VaultState.UNLOCKED);
|
LOG.warn("Lock of {} failed.", vault.getDisplayName(), throwable);
|
||||||
lockWindow.setScene(lockFailedScene.get());
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
lockWindow.show();
|
if (throwable instanceof Volume.VolumeException) {
|
||||||
|
lockWindow.setScene(lockFailedScene.get());
|
||||||
|
lockWindow.show();
|
||||||
|
} else {
|
||||||
|
errorComponent.cause(throwable).window(lockWindow).build().showErrorScene();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cancelled() {
|
protected void cancelled() {
|
||||||
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
|
LOG.debug("Lock of {} canceled.", vault.getDisplayName());
|
||||||
vault.setState(VaultState.UNLOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.cryptomator.ui.mainwindow;
|
|||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptofs.DirStructure;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -20,6 +22,7 @@ import javafx.scene.input.TransferMode;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -27,6 +30,7 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class MainWindowController implements FxController {
|
public class MainWindowController implements FxController {
|
||||||
@@ -91,23 +95,21 @@ public class MainWindowController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean containsVault(Path path) {
|
private boolean containsVault(Path path) {
|
||||||
if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
try {
|
||||||
return true;
|
return CryptoFileSystemProvider.checkDirStructureForVault(path, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) != DirStructure.UNRELATED;
|
||||||
} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
|
} catch (IOException e) {
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addVault(Path pathToVault) {
|
private void addVault(Path pathToVault) {
|
||||||
try {
|
try {
|
||||||
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
if (pathToVault.getFileName().toString().equals(VAULTCONFIG_FILENAME)) {
|
||||||
vaultListManager.add(pathToVault.getParent());
|
vaultListManager.add(pathToVault.getParent());
|
||||||
} else {
|
} else {
|
||||||
vaultListManager.add(pathToVault);
|
vaultListManager.add(pathToVault);
|
||||||
}
|
}
|
||||||
} catch (NoSuchFileException e) {
|
} catch (IOException e) {
|
||||||
LOG.debug("Not a vault: {}", pathToVault);
|
LOG.debug("Not a vault: {}", pathToVault);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -39,8 +39,8 @@ abstract class MainWindowModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@MainWindow
|
@MainWindow
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -60,8 +60,8 @@ abstract class MainWindowModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMainScene(@MainWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/main_window.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MAIN_WINDOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
@@ -86,6 +86,11 @@ abstract class MainWindowModule {
|
|||||||
@FxControllerKey(VaultListController.class)
|
@FxControllerKey(VaultListController.class)
|
||||||
abstract FxController bindVaultListController(VaultListController controller);
|
abstract FxController bindVaultListController(VaultListController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(VaultListContextMenuController.class)
|
||||||
|
abstract FxController bindVaultListContextMenuController(VaultListContextMenuController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(VaultDetailController.class)
|
@FxControllerKey(VaultDetailController.class)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class MainWindowTitleController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showDonationKeyPreferences() {
|
public void showDonationKeyPreferences() {
|
||||||
application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
|
application.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ public class VaultDetailController implements FxController {
|
|||||||
this.anyVaultSelected = vault.isNotNull();
|
this.anyVaultSelected = vault.isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
// TODO deduplicate w/ VaultListCellController
|
||||||
|
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
return switch (state) {
|
return switch (state) {
|
||||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||||
|
|||||||
+7
-1
@@ -5,6 +5,7 @@ import org.cryptomator.common.keychain.KeychainManager;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
|
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -47,7 +48,12 @@ public class VaultDetailLockedController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showVaultOptions() {
|
public void showVaultOptions() {
|
||||||
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow();
|
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void showKeyVaultOptions() {
|
||||||
|
vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ public class VaultListCellController implements FxController {
|
|||||||
.map(this::getGlyphForVaultState);
|
.map(this::getGlyphForVaultState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FontAwesome5Icon getGlyphForVaultState(VaultState state) {
|
// TODO deduplicate w/ VaultDetailController
|
||||||
|
private FontAwesome5Icon getGlyphForVaultState(VaultState.Value state) {
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
return switch (state) {
|
return switch (state) {
|
||||||
case LOCKED -> FontAwesome5Icon.LOCK;
|
case LOCKED -> FontAwesome5Icon.LOCK;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.controls.DraggableListCell;
|
import org.cryptomator.ui.controls.DraggableListCell;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -17,10 +17,10 @@ import java.io.UncheckedIOException;
|
|||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListCellFactory implements Callback<ListView<Vault>, ListCell<Vault>> {
|
public class VaultListCellFactory implements Callback<ListView<Vault>, ListCell<Vault>> {
|
||||||
|
|
||||||
private final FXMLLoaderFactory fxmlLoaders;
|
private final FxmlLoaderFactory fxmlLoaders;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListCellFactory(@MainWindow FXMLLoaderFactory fxmlLoaders) {
|
VaultListCellFactory(@MainWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
this.fxmlLoaders = fxmlLoaders;
|
this.fxmlLoaders = fxmlLoaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import com.tobiasdiez.easybind.EasyBind;
|
||||||
|
import com.tobiasdiez.easybind.optional.ObservableOptionalValue;
|
||||||
|
import com.tobiasdiez.easybind.optional.OptionalBinding;
|
||||||
|
import org.cryptomator.common.keychain.KeychainManager;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
|
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||||
|
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
|
||||||
|
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||||
|
|
||||||
|
@MainWindowScoped
|
||||||
|
public class VaultListContextMenuController implements FxController {
|
||||||
|
|
||||||
|
private final ObservableOptionalValue<Vault> selectedVault;
|
||||||
|
private final Stage mainWindow;
|
||||||
|
private final FxApplication application;
|
||||||
|
private final KeychainManager keychain;
|
||||||
|
private final RemoveVaultComponent.Builder removeVault;
|
||||||
|
private final VaultOptionsComponent.Builder vaultOptionsWindow;
|
||||||
|
private final OptionalBinding<VaultState.Value> selectedVaultState;
|
||||||
|
private final Binding<Boolean> selectedVaultPassphraseStored;
|
||||||
|
private final Binding<Boolean> selectedVaultRemovable;
|
||||||
|
private final Binding<Boolean> selectedVaultUnlockable;
|
||||||
|
private final Binding<Boolean> selectedVaultLockable;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplication application, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) {
|
||||||
|
this.selectedVault = EasyBind.wrapNullable(selectedVault);
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.application = application;
|
||||||
|
this.keychain = keychain;
|
||||||
|
this.removeVault = removeVault;
|
||||||
|
this.vaultOptionsWindow = vaultOptionsWindow;
|
||||||
|
|
||||||
|
this.selectedVaultState = this.selectedVault.mapObservable(Vault::stateProperty);
|
||||||
|
this.selectedVaultPassphraseStored = this.selectedVault.map(this::isPasswordStored).orElse(false);
|
||||||
|
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
|
||||||
|
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
|
||||||
|
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPasswordStored(Vault vault) {
|
||||||
|
return keychain.getPassphraseStoredProperty(vault.getId()).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickRemoveVault() {
|
||||||
|
selectedVault.ifValuePresent(v -> {
|
||||||
|
removeVault.vault(v).build().showRemoveVault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickShowVaultOptions() {
|
||||||
|
selectedVault.ifValuePresent(v -> {
|
||||||
|
vaultOptionsWindow.vault(v).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickUnlockVault() {
|
||||||
|
selectedVault.ifValuePresent(v -> {
|
||||||
|
application.startUnlockWorkflow(v, Optional.of(mainWindow));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickLockVault() {
|
||||||
|
selectedVault.ifValuePresent(v -> {
|
||||||
|
application.startLockWorkflow(v, Optional.of(mainWindow));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void didClickRevealVault() {
|
||||||
|
selectedVault.ifValuePresent(v -> {
|
||||||
|
application.getVaultService().reveal(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter and Setter
|
||||||
|
|
||||||
|
public Binding<Boolean> selectedVaultUnlockableProperty() {
|
||||||
|
return selectedVaultUnlockable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelectedVaultUnlockable() {
|
||||||
|
return selectedVaultUnlockable.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding<Boolean> selectedVaultLockableProperty() {
|
||||||
|
return selectedVaultLockable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelectedVaultLockable() {
|
||||||
|
return selectedVaultLockable.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding<Boolean> selectedVaultRemovableProperty() {
|
||||||
|
return selectedVaultRemovable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelectedVaultRemovable() {
|
||||||
|
return selectedVaultRemovable.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Binding<Boolean> selectedVaultPassphraseStoredProperty() {
|
||||||
|
return selectedVaultPassphraseStored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelectedVaultPassphraseStored() {
|
||||||
|
return selectedVaultPassphraseStored.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
@@ -17,30 +16,43 @@ import javafx.collections.ListChangeListener;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultListController implements FxController {
|
public class VaultListController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
|
||||||
|
|
||||||
|
private final Stage mainWindow;
|
||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
private final ObjectProperty<Vault> selectedVault;
|
private final ObjectProperty<Vault> selectedVault;
|
||||||
private final VaultListCellFactory cellFactory;
|
private final VaultListCellFactory cellFactory;
|
||||||
private final AddVaultWizardComponent.Builder addVaultWizard;
|
private final AddVaultWizardComponent.Builder addVaultWizard;
|
||||||
private final RemoveVaultComponent.Builder removeVault;
|
|
||||||
private final BooleanBinding noVaultSelected;
|
|
||||||
private final BooleanBinding emptyVaultList;
|
private final BooleanBinding emptyVaultList;
|
||||||
|
private final RemoveVaultComponent.Builder removeVaultDialogue;
|
||||||
|
|
||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
VaultListController(@MainWindow Stage mainWindow, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVaultDialogue) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
this.cellFactory = cellFactory;
|
this.cellFactory = cellFactory;
|
||||||
this.addVaultWizard = addVaultWizard;
|
this.addVaultWizard = addVaultWizard;
|
||||||
this.removeVault = removeVault;
|
this.removeVaultDialogue = removeVaultDialogue;
|
||||||
this.noVaultSelected = selectedVault.isNull();
|
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
|
|
||||||
selectedVault.addListener(this::selectedVaultDidChange);
|
selectedVault.addListener(this::selectedVaultDidChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +68,41 @@ public class VaultListController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
vaultList.addEventFilter(MouseEvent.MOUSE_RELEASED, this::deselect);
|
||||||
|
vaultList.addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, request -> {
|
||||||
|
if (selectedVault.get() == null) {
|
||||||
|
request.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
vaultList.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
|
||||||
|
if (keyEvent.getCode() == KeyCode.DELETE) {
|
||||||
|
pressedShortcutToRemoveVault();
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (SystemUtils.IS_OS_MAC) {
|
||||||
|
vaultList.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
|
||||||
|
if (keyEvent.getCode() == KeyCode.BACK_SPACE) {
|
||||||
|
pressedShortcutToRemoveVault();
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//register vault selection shortcut to the main window
|
||||||
|
mainWindow.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
|
||||||
|
if (keyEvent.isShortcutDown() && keyEvent.getCode().isDigitKey()) {
|
||||||
|
vaultList.getSelectionModel().select(Integer.parseInt(keyEvent.getText()) - 1);
|
||||||
|
keyEvent.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deselect(MouseEvent released) {
|
||||||
|
if (released.getY() > (vaultList.getItems().size() * vaultList.fixedCellSizeProperty().get())) {
|
||||||
|
vaultList.getSelectionModel().clearSelection();
|
||||||
|
released.consume();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
|
private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
|
||||||
@@ -70,13 +117,10 @@ public class VaultListController implements FxController {
|
|||||||
addVaultWizard.build().showAddVaultWizard();
|
addVaultWizard.build().showAddVaultWizard();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
private void pressedShortcutToRemoveVault() {
|
||||||
public void didClickRemoveVault() {
|
final var vault = selectedVault.get();
|
||||||
Vault v = selectedVault.get();
|
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
|
||||||
if (v != null) {
|
removeVaultDialogue.vault(vault).build().showRemoveVault();
|
||||||
removeVault.vault(v).build().showRemoveVault();
|
|
||||||
} else {
|
|
||||||
LOG.debug("Cannot remove a vault if none is selected.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +134,4 @@ public class VaultListController implements FxController {
|
|||||||
return emptyVaultList.get();
|
return emptyVaultList.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding noVaultSelectedProperty() {
|
|
||||||
return noVaultSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNoVaultSelected() {
|
|
||||||
return noVaultSelected.get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -30,8 +30,8 @@ abstract class MigrationModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@MigrationWindow
|
@MigrationWindow
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -56,36 +56,36 @@ abstract class MigrationModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_START)
|
@FxmlScene(FxmlFile.MIGRATION_START)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationStartScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationStartScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_start.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_START);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_RUN)
|
@FxmlScene(FxmlFile.MIGRATION_RUN)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationRunScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationRunScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_run.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_RUN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_SUCCESS)
|
@FxmlScene(FxmlFile.MIGRATION_SUCCESS)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationSuccessScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationSuccessScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_success.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
|
@FxmlScene(FxmlFile.MIGRATION_CAPABILITY_ERROR)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationCapabilityErrorScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_capability_error.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_CAPABILITY_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE)
|
@FxmlScene(FxmlFile.MIGRATION_IMPOSSIBLE)
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
static Scene provideMigrationImpossibleScene(@MigrationWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideMigrationImpossibleScene(@MigrationWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/migration_impossible.fxml");
|
return fxmlLoaders.createScene(FxmlFile.MIGRATION_IMPOSSIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import javax.inject.Named;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
|
import javafx.beans.binding.ObjectExpression;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.DoubleProperty;
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -43,6 +44,7 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
|
||||||
@MigrationScoped
|
@MigrationScoped
|
||||||
public class MigrationRunController implements FxController {
|
public class MigrationRunController implements FxController {
|
||||||
@@ -89,7 +91,12 @@ public class MigrationRunController implements FxController {
|
|||||||
if (keychain.isSupported()) {
|
if (keychain.isSupported()) {
|
||||||
loadStoredPassword();
|
loadStoredPassword();
|
||||||
}
|
}
|
||||||
migrationButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.NEEDS_MIGRATION).or(passwordField.textProperty().isEmpty()));
|
|
||||||
|
migrationButtonDisabled.bind(ObjectExpression.objectExpression(vault.stateProperty())
|
||||||
|
.isNotEqualTo(VaultState.Value.NEEDS_MIGRATION)
|
||||||
|
.or(passwordField.textProperty().isEmpty()));
|
||||||
|
|
||||||
|
window.setOnHiding(event -> passwordField.wipe());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -101,7 +108,7 @@ public class MigrationRunController implements FxController {
|
|||||||
public void migrate() {
|
public void migrate() {
|
||||||
LOG.info("Migrating vault {}", vault.getPath());
|
LOG.info("Migrating vault {}", vault.getPath());
|
||||||
CharSequence password = passwordField.getCharacters();
|
CharSequence password = passwordField.getCharacters();
|
||||||
vault.setState(VaultState.PROCESSING);
|
vault.stateProperty().transition(VaultState.Value.NEEDS_MIGRATION, VaultState.Value.PROCESSING);
|
||||||
passwordField.setDisable(true);
|
passwordField.setDisable(true);
|
||||||
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
|
ScheduledFuture<?> progressSyncTask = scheduler.scheduleAtFixedRate(() -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -110,15 +117,15 @@ public class MigrationRunController implements FxController {
|
|||||||
}, 0, MIGRATION_PROGRESS_UPDATE_MILLIS, TimeUnit.MILLISECONDS);
|
}, 0, MIGRATION_PROGRESS_UPDATE_MILLIS, TimeUnit.MILLISECONDS);
|
||||||
Tasks.create(() -> {
|
Tasks.create(() -> {
|
||||||
Migrators migrators = Migrators.get();
|
Migrators migrators = Migrators.get();
|
||||||
migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged, this::migrationRequiresInput);
|
migrators.migrate(vault.getPath(), VAULTCONFIG_FILENAME, MASTERKEY_FILENAME, password, this::migrationProgressChanged, this::migrationRequiresInput);
|
||||||
return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME);
|
return migrators.needsMigration(vault.getPath(), VAULTCONFIG_FILENAME, MASTERKEY_FILENAME);
|
||||||
}).onSuccess(needsAnotherMigration -> {
|
}).onSuccess(needsAnotherMigration -> {
|
||||||
if (needsAnotherMigration) {
|
if (needsAnotherMigration) {
|
||||||
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());
|
LOG.info("Migration of '{}' succeeded, but another migration is required.", vault.getDisplayName());
|
||||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
|
LOG.info("Migration of '{}' succeeded.", vault.getDisplayName());
|
||||||
vault.setState(VaultState.LOCKED);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
passwordField.wipe();
|
passwordField.wipe();
|
||||||
window.setScene(successScene.get());
|
window.setScene(successScene.get());
|
||||||
}
|
}
|
||||||
@@ -127,20 +134,20 @@ public class MigrationRunController implements FxController {
|
|||||||
passwordField.setDisable(false);
|
passwordField.setDisable(false);
|
||||||
passwordField.selectAll();
|
passwordField.selectAll();
|
||||||
passwordField.requestFocus();
|
passwordField.requestFocus();
|
||||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||||
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
|
}).onError(FileSystemCapabilityChecker.MissingCapabilityException.class, e -> {
|
||||||
LOG.error("Underlying file system not supported.", e);
|
LOG.error("Underlying file system not supported.", e);
|
||||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||||
missingCapability.set(e.getMissingCapability());
|
missingCapability.set(e.getMissingCapability());
|
||||||
window.setScene(capabilityErrorScene.get());
|
window.setScene(capabilityErrorScene.get());
|
||||||
}).onError(FileNameTooLongException.class, e -> {
|
}).onError(FileNameTooLongException.class, e -> {
|
||||||
LOG.error("Migration failed because the underlying file system does not support long filenames.", e);
|
LOG.error("Migration failed because the underlying file system does not support long filenames.", e);
|
||||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||||
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
||||||
window.setScene(impossibleScene.get());
|
window.setScene(impossibleScene.get());
|
||||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||||
LOG.error("Migration failed for technical reasons.", e);
|
LOG.error("Migration failed for technical reasons.", e);
|
||||||
vault.setState(VaultState.NEEDS_MIGRATION);
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.NEEDS_MIGRATION);
|
||||||
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
errorComponent.cause(e).window(window).returnToScene(startScene.get()).build().showErrorScene();
|
||||||
}).andFinally(() -> {
|
}).andFinally(() -> {
|
||||||
passwordField.setDisable(false);
|
passwordField.setDisable(false);
|
||||||
|
|||||||
+2
-2
@@ -157,8 +157,8 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void showDonationTab() {
|
public void showContributeTab() {
|
||||||
selectedTabProperty.set(SelectedPreferencesTab.DONATION_KEY);
|
selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class PreferencesController implements FxController {
|
|||||||
public Tab generalTab;
|
public Tab generalTab;
|
||||||
public Tab volumeTab;
|
public Tab volumeTab;
|
||||||
public Tab updatesTab;
|
public Tab updatesTab;
|
||||||
public Tab donationKeyTab;
|
public Tab contributeTab;
|
||||||
public Tab aboutTab;
|
public Tab aboutTab;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -52,7 +52,7 @@ public class PreferencesController implements FxController {
|
|||||||
return switch (selectedTab) {
|
return switch (selectedTab) {
|
||||||
case UPDATES -> updatesTab;
|
case UPDATES -> updatesTab;
|
||||||
case VOLUME -> volumeTab;
|
case VOLUME -> volumeTab;
|
||||||
case DONATION_KEY -> donationKeyTab;
|
case CONTRIBUTE -> contributeTab;
|
||||||
case GENERAL -> generalTab;
|
case GENERAL -> generalTab;
|
||||||
case ABOUT -> aboutTab;
|
case ABOUT -> aboutTab;
|
||||||
case ANY -> updateAvailable.get() ? updatesTab : generalTab;
|
case ANY -> updateAvailable.get() ? updatesTab : generalTab;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -32,8 +32,8 @@ abstract class PreferencesModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@PreferencesWindow
|
@PreferencesWindow
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -49,8 +49,8 @@ abstract class PreferencesModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.PREFERENCES)
|
@FxmlScene(FxmlFile.PREFERENCES)
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene providePreferencesScene(@PreferencesWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/preferences.fxml");
|
return fxmlLoaders.createScene(FxmlFile.PREFERENCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
@@ -77,8 +77,8 @@ abstract class PreferencesModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(DonationKeyPreferencesController.class)
|
@FxControllerKey(SupporterCertificateController.class)
|
||||||
abstract FxController bindDonationKeyPreferencesController(DonationKeyPreferencesController controller);
|
abstract FxController bindSupporterCertificatePreferencesController(SupporterCertificateController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ public enum SelectedPreferencesTab {
|
|||||||
UPDATES,
|
UPDATES,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show donation key tab
|
* Show contribute tab
|
||||||
*/
|
*/
|
||||||
DONATION_KEY,
|
CONTRIBUTE,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show about tab
|
* Show about tab
|
||||||
|
|||||||
+9
-9
@@ -14,17 +14,17 @@ import javafx.scene.control.TextArea;
|
|||||||
import javafx.scene.control.TextFormatter;
|
import javafx.scene.control.TextFormatter;
|
||||||
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class DonationKeyPreferencesController implements FxController {
|
public class SupporterCertificateController implements FxController {
|
||||||
|
|
||||||
private static final String DONATION_URI = "https://store.cryptomator.org/desktop";
|
private static final String SUPPORTER_URI = "https://store.cryptomator.org/desktop";
|
||||||
|
|
||||||
private final Application application;
|
private final Application application;
|
||||||
private final LicenseHolder licenseHolder;
|
private final LicenseHolder licenseHolder;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
public TextArea donationKeyField;
|
public TextArea supporterCertificateField;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DonationKeyPreferencesController(Application application, LicenseHolder licenseHolder, Settings settings) {
|
SupporterCertificateController(Application application, LicenseHolder licenseHolder, Settings settings) {
|
||||||
this.application = application;
|
this.application = application;
|
||||||
this.licenseHolder = licenseHolder;
|
this.licenseHolder = licenseHolder;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@@ -32,9 +32,9 @@ public class DonationKeyPreferencesController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
|
supporterCertificateField.setText(licenseHolder.getLicenseKey().orElse(null));
|
||||||
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
|
supporterCertificateField.textProperty().addListener(this::registrationKeyChanged);
|
||||||
donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
|
supporterCertificateField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
|
private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
|
||||||
@@ -53,8 +53,8 @@ public class DonationKeyPreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void getDonationKey() {
|
public void getSupporterCertificate() {
|
||||||
application.getHostServices().showDocument(DONATION_URI);
|
application.getHostServices().showDocument(SUPPORTER_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LicenseHolder getLicenseHolder() {
|
public LicenseHolder getLicenseHolder() {
|
||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -25,8 +25,8 @@ abstract class QuitModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@QuitWindow
|
@QuitWindow
|
||||||
@QuitScoped
|
@QuitScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -43,8 +43,8 @@ abstract class QuitModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.QUIT)
|
@FxmlScene(FxmlFile.QUIT)
|
||||||
@QuitScoped
|
@QuitScoped
|
||||||
static Scene provideQuitScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideQuitScene(@QuitWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/quit.fxml");
|
return fxmlLoaders.createScene(FxmlFile.QUIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
+2
-1
@@ -2,6 +2,7 @@ package org.cryptomator.ui.recoverykey;
|
|||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
import org.cryptomator.ui.common.Animations;
|
import org.cryptomator.ui.common.Animations;
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
@@ -80,7 +81,7 @@ public class RecoveryKeyCreationController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String call() throws IOException {
|
protected String call() throws IOException, CryptoException {
|
||||||
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
|
return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,28 +2,34 @@ package org.cryptomator.ui.recoverykey;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.common.MasterkeyBackupHelper;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.cryptolib.api.Masterkey;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class RecoveryKeyFactory {
|
public class RecoveryKeyFactory {
|
||||||
|
|
||||||
private static final byte[] PEPPER = new byte[0];
|
|
||||||
|
|
||||||
private final WordEncoder wordEncoder;
|
private final WordEncoder wordEncoder;
|
||||||
|
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecoveryKeyFactory(WordEncoder wordEncoder) {
|
public RecoveryKeyFactory(WordEncoder wordEncoder, MasterkeyFileAccess masterkeyFileAccess) {
|
||||||
this.wordEncoder = wordEncoder;
|
this.wordEncoder = wordEncoder;
|
||||||
|
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<String> getDictionary() {
|
public Collection<String> getDictionary() {
|
||||||
@@ -36,11 +42,14 @@ public class RecoveryKeyFactory {
|
|||||||
* @return The recovery key of the vault at the given path
|
* @return The recovery key of the vault at the given path
|
||||||
* @throws IOException If the masterkey file could not be read
|
* @throws IOException If the masterkey file could not be read
|
||||||
* @throws InvalidPassphraseException If the provided password is wrong
|
* @throws InvalidPassphraseException If the provided password is wrong
|
||||||
|
* @throws CryptoException In case of other cryptographic errors
|
||||||
* @apiNote This is a long-running operation and should be invoked in a background thread
|
* @apiNote This is a long-running operation and should be invoked in a background thread
|
||||||
*/
|
*/
|
||||||
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException {
|
public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException, CryptoException {
|
||||||
byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, PEPPER, password);
|
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
|
||||||
try {
|
byte[] rawKey = new byte[0];
|
||||||
|
try (var masterkey = masterkeyFileAccess.load(masterkeyPath, password)) {
|
||||||
|
rawKey = masterkey.getEncoded();
|
||||||
return createRecoveryKey(rawKey);
|
return createRecoveryKey(rawKey);
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(rawKey, (byte) 0x00);
|
Arrays.fill(rawKey, (byte) 0x00);
|
||||||
@@ -72,8 +81,15 @@ public class RecoveryKeyFactory {
|
|||||||
*/
|
*/
|
||||||
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
|
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
|
||||||
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
|
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
|
||||||
try {
|
try (var masterkey = new Masterkey(rawKey)) {
|
||||||
CryptoFileSystemProvider.restoreRawKey(vaultPath, MASTERKEY_FILENAME, rawKey, PEPPER, newPassword);
|
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
|
||||||
|
if (Files.exists(masterkeyPath)) {
|
||||||
|
byte[] oldMasterkeyBytes = Files.readAllBytes(masterkeyPath);
|
||||||
|
// TODO: deduplicate with ChangePasswordController:
|
||||||
|
Path backupKeyPath = vaultPath.resolve(MASTERKEY_FILENAME + MasterkeyBackupHelper.generateFileIdSuffix(oldMasterkeyBytes) + MASTERKEY_BACKUP_SUFFIX);
|
||||||
|
Files.move(masterkeyPath, backupKeyPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
}
|
||||||
|
masterkeyFileAccess.persist(masterkey, masterkeyPath, newPassword);
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(rawKey, (byte) 0x00);
|
Arrays.fill(rawKey, (byte) 0x00);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
|||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -33,8 +33,8 @@ abstract class RecoveryKeyModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@RecoveryKeyWindow
|
@RecoveryKeyWindow
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -69,29 +69,29 @@ abstract class RecoveryKeyModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
|
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeyCreationScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeyCreationScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/recoverykey_create.fxml");
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
|
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeyRecoverScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeyRecoverScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/recoverykey_recover.fxml");
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RECOVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD)
|
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeyResetPasswordScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeyResetPasswordScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/recoverykey_reset_password.fxml");
|
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -26,8 +26,8 @@ abstract class RemoveVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@RemoveVaultWindow
|
@RemoveVaultWindow
|
||||||
@RemoveVaultScoped
|
@RemoveVaultScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -45,8 +45,8 @@ abstract class RemoveVaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.REMOVE_VAULT)
|
@FxmlScene(FxmlFile.REMOVE_VAULT)
|
||||||
@RemoveVaultScoped
|
@RemoveVaultScoped
|
||||||
static Scene provideRemoveVaultScene(@RemoveVaultWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRemoveVaultScene(@RemoveVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/remove_vault.fxml");
|
return fxmlLoaders.createScene(FxmlFile.REMOVE_VAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import javafx.stage.Stage;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -29,8 +29,8 @@ abstract class VaultStatisticsModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@VaultStatisticsWindow
|
@VaultStatisticsWindow
|
||||||
@VaultStatisticsScoped
|
@VaultStatisticsScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -43,8 +43,8 @@ abstract class VaultStatisticsModule {
|
|||||||
var weakStage = new WeakReference<>(stage);
|
var weakStage = new WeakReference<>(stage);
|
||||||
vault.stateProperty().addListener(new ChangeListener<>() {
|
vault.stateProperty().addListener(new ChangeListener<>() {
|
||||||
@Override
|
@Override
|
||||||
public void changed(ObservableValue<? extends VaultState> observable, VaultState oldValue, VaultState newValue) {
|
public void changed(ObservableValue<? extends VaultState.Value> observable, VaultState.Value oldValue, VaultState.Value newValue) {
|
||||||
if (newValue != VaultState.UNLOCKED) {
|
if (newValue != VaultState.Value.UNLOCKED) {
|
||||||
Stage stage = weakStage.get();
|
Stage stage = weakStage.get();
|
||||||
if (stage != null) {
|
if (stage != null) {
|
||||||
stage.hide();
|
stage.hide();
|
||||||
@@ -60,8 +60,8 @@ abstract class VaultStatisticsModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.VAULT_STATISTICS)
|
@FxmlScene(FxmlFile.VAULT_STATISTICS)
|
||||||
@VaultStatisticsScoped
|
@VaultStatisticsScoped
|
||||||
static Scene provideVaultStatisticsScene(@VaultStatisticsWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideVaultStatisticsScene(@VaultStatisticsWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/stats.fxml");
|
return fxmlLoaders.createScene(FxmlFile.VAULT_STATISTICS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ class TrayMenuController {
|
|||||||
|
|
||||||
public void initTrayMenu() {
|
public void initTrayMenu() {
|
||||||
vaults.addListener(this::vaultListChanged);
|
vaults.addListener(this::vaultListChanged);
|
||||||
|
vaults.forEach(v -> {
|
||||||
|
v.displayNameProperty().addListener(this::vaultListChanged);
|
||||||
|
});
|
||||||
rebuildMenu();
|
rebuildMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
|
|
||||||
|
public class UnlockCancelledException extends MasterkeyLoadingFailedException {
|
||||||
|
|
||||||
|
public UnlockCancelledException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnlockCancelledException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-9
@@ -1,15 +1,11 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
import dagger.Lazy;
|
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
import org.cryptomator.common.vaults.MountPointRequirement;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
//At the current point in time only the CustomMountPointChooser may cause this window to be shown.
|
||||||
@@ -17,19 +13,17 @@ import javafx.stage.Stage;
|
|||||||
public class UnlockInvalidMountPointController implements FxController {
|
public class UnlockInvalidMountPointController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> unlockScene;
|
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @UnlockWindow Vault vault) {
|
UnlockInvalidMountPointController(@UnlockWindow Stage window, @UnlockWindow Vault vault) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.unlockScene = unlockScene;
|
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void back() {
|
public void close() {
|
||||||
window.setScene(unlockScene.get());
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
import org.cryptomator.ui.common.UserInteractionLock;
|
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@@ -27,59 +23,15 @@ import javafx.stage.Stage;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
@Module(subcomponents = {ForgetPasswordComponent.class})
|
@Module(subcomponents = {KeyLoadingComponent.class})
|
||||||
abstract class UnlockModule {
|
abstract class UnlockModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockModule.class);
|
|
||||||
|
|
||||||
public enum PasswordEntry {
|
|
||||||
PASSWORD_ENTERED,
|
|
||||||
CANCELED
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@UnlockScoped
|
|
||||||
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
|
|
||||||
return new UserInteractionLock<>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Named("savedPassword")
|
|
||||||
@UnlockScoped
|
|
||||||
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @UnlockWindow Vault vault) {
|
|
||||||
if (!keychain.isSupported()) {
|
|
||||||
return Optional.empty();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
|
|
||||||
} catch (KeychainAccessException e) {
|
|
||||||
LOG.error("Failed to load entry from system keychain.", e);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@UnlockScoped
|
|
||||||
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
|
|
||||||
return new AtomicReference(storedPassword.orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Named("savePassword")
|
|
||||||
@UnlockScoped
|
|
||||||
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
|
|
||||||
return new AtomicBoolean(storedPassword.isPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@UnlockWindow
|
@UnlockWindow
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -99,33 +51,28 @@ abstract class UnlockModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.UNLOCK)
|
@UnlockWindow
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
static Scene provideUnlockScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Builder compBuilder, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
|
||||||
return fxmlLoaders.createScene("/fxml/unlock.fxml");
|
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
|
@FxmlScene(FxmlFile.UNLOCK_SUCCESS)
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
static Scene provideUnlockSuccessScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideUnlockSuccessScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
|
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
static Scene provideInvalidMountPointScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideInvalidMountPointScene(@UnlockWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/unlock_invalid_mount_point.fxml");
|
return fxmlLoaders.createScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@FxControllerKey(UnlockController.class)
|
|
||||||
abstract FxController bindUnlockController(UnlockController controller);
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(UnlockSuccessController.class)
|
@FxControllerKey(UnlockSuccessController.class)
|
||||||
|
|||||||
@@ -1,40 +1,31 @@
|
|||||||
package org.cryptomator.ui.unlock;
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.cryptomator.common.keychain.KeychainManager;
|
|
||||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.vaults.MountPointRequirement;
|
import org.cryptomator.common.vaults.MountPointRequirement;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
import org.cryptomator.common.vaults.Volume.VolumeException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
import org.cryptomator.ui.common.Animations;
|
|
||||||
import org.cryptomator.ui.common.ErrorComponent;
|
import org.cryptomator.ui.common.ErrorComponent;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.UserInteractionLock;
|
|
||||||
import org.cryptomator.ui.common.VaultService;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.cryptomator.ui.unlock.UnlockModule.PasswordEntry;
|
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||||
|
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.Window;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.nio.file.NotDirectoryException;
|
import java.nio.file.NotDirectoryException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multi-step task that consists of background activities as well as user interaction.
|
* A multi-step task that consists of background activities as well as user interaction.
|
||||||
@@ -49,113 +40,47 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final VaultService vaultService;
|
private final VaultService vaultService;
|
||||||
private final AtomicReference<char[]> password;
|
|
||||||
private final AtomicBoolean savePassword;
|
|
||||||
private final Optional<char[]> savedPassword;
|
|
||||||
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
|
|
||||||
private final KeychainManager keychain;
|
|
||||||
private final Lazy<Scene> unlockScene;
|
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
private final Lazy<Scene> invalidMountPointScene;
|
private final Lazy<Scene> invalidMountPointScene;
|
||||||
private final ErrorComponent.Builder errorComponent;
|
private final ErrorComponent.Builder errorComponent;
|
||||||
|
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, KeychainManager keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
|
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent, @UnlockWindow KeyLoadingStrategy keyLoadingStrategy) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.vaultService = vaultService;
|
this.vaultService = vaultService;
|
||||||
this.password = password;
|
|
||||||
this.savePassword = savePassword;
|
|
||||||
this.savedPassword = savedPassword;
|
|
||||||
this.passwordEntryLock = passwordEntryLock;
|
|
||||||
this.keychain = keychain;
|
|
||||||
this.unlockScene = unlockScene;
|
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.invalidMountPointScene = invalidMountPointScene;
|
this.invalidMountPointScene = invalidMountPointScene;
|
||||||
this.errorComponent = errorComponent;
|
this.errorComponent = errorComponent;
|
||||||
|
this.keyLoadingStrategy = keyLoadingStrategy;
|
||||||
setOnFailed(event -> {
|
|
||||||
Throwable throwable = event.getSource().getException();
|
|
||||||
if (throwable instanceof InvalidMountPointException) {
|
|
||||||
handleInvalidMountPoint((InvalidMountPointException) throwable);
|
|
||||||
} else {
|
|
||||||
handleGenericError(throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
|
protected Boolean call() throws InterruptedException, IOException, VolumeException, InvalidMountPointException, CryptoException {
|
||||||
try {
|
try {
|
||||||
if (attemptUnlock()) {
|
attemptUnlock();
|
||||||
handleSuccess();
|
return true;
|
||||||
return true;
|
} catch (UnlockCancelledException e) {
|
||||||
|
cancel(false); // set Tasks state to cancelled
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptUnlock() throws IOException, VolumeException, InvalidMountPointException, CryptoException {
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
vault.unlock(keyLoadingStrategy);
|
||||||
|
success = true;
|
||||||
|
} catch (MasterkeyLoadingFailedException e) {
|
||||||
|
if (keyLoadingStrategy.recoverFromException(e)) {
|
||||||
|
LOG.info("Unlock attempt threw {}. Reattempting...", e.getClass().getSimpleName());
|
||||||
|
attemptUnlock();
|
||||||
} else {
|
} else {
|
||||||
cancel(false); // set Tasks state to cancelled
|
throw e;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
wipePassword(password.get());
|
keyLoadingStrategy.cleanup(success);
|
||||||
wipePassword(savedPassword.orElse(null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean attemptUnlock() throws InterruptedException, IOException, VolumeException, InvalidMountPointException {
|
|
||||||
boolean proceed = password.get() != null || askForPassword(false) == PasswordEntry.PASSWORD_ENTERED;
|
|
||||||
while (proceed) {
|
|
||||||
try {
|
|
||||||
vault.unlock(CharBuffer.wrap(password.get()));
|
|
||||||
return true;
|
|
||||||
} catch (InvalidPassphraseException e) {
|
|
||||||
proceed = askForPassword(true) == PasswordEntry.PASSWORD_ENTERED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PasswordEntry askForPassword(boolean animateShake) throws InterruptedException {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
window.setScene(unlockScene.get());
|
|
||||||
window.show();
|
|
||||||
Window owner = window.getOwner();
|
|
||||||
if (owner != null) {
|
|
||||||
window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2);
|
|
||||||
window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2);
|
|
||||||
} else {
|
|
||||||
window.centerOnScreen();
|
|
||||||
}
|
|
||||||
if (animateShake) {
|
|
||||||
Animations.createShakeWindowAnimation(window).play();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return passwordEntryLock.awaitInteraction();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSuccess() {
|
|
||||||
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
|
|
||||||
if (savePassword.get()) {
|
|
||||||
savePasswordToSystemkeychain();
|
|
||||||
}
|
|
||||||
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
|
|
||||||
case ASK -> Platform.runLater(() -> {
|
|
||||||
window.setScene(successScene.get());
|
|
||||||
window.show();
|
|
||||||
});
|
|
||||||
case REVEAL -> {
|
|
||||||
Platform.runLater(window::close);
|
|
||||||
vaultService.reveal(vault);
|
|
||||||
}
|
|
||||||
case IGNORE -> Platform.runLater(window::close);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void savePasswordToSystemkeychain() {
|
|
||||||
if (keychain.isSupported()) {
|
|
||||||
try {
|
|
||||||
keychain.storePassphrase(vault.getId(), CharBuffer.wrap(password.get()));
|
|
||||||
} catch (KeychainAccessException e) {
|
|
||||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,15 +98,12 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
LOG.error("Unlock failed. Mountpoint doesn't exist (needs to be a folder): {}", cause.getMessage());
|
LOG.error("Unlock failed. Mountpoint doesn't exist (needs to be a folder): {}", cause.getMessage());
|
||||||
}
|
}
|
||||||
showInvalidMountPointScene();
|
showInvalidMountPointScene();
|
||||||
return;
|
|
||||||
} else if (cause instanceof FileAlreadyExistsException) {
|
} else if (cause instanceof FileAlreadyExistsException) {
|
||||||
LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage());
|
LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage());
|
||||||
showInvalidMountPointScene();
|
showInvalidMountPointScene();
|
||||||
return;
|
|
||||||
} else if (cause instanceof DirectoryNotEmptyException) {
|
} else if (cause instanceof DirectoryNotEmptyException) {
|
||||||
LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage());
|
LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage());
|
||||||
showInvalidMountPointScene();
|
showInvalidMountPointScene();
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
handleGenericError(impExc);
|
handleGenericError(impExc);
|
||||||
}
|
}
|
||||||
@@ -196,33 +118,44 @@ public class UnlockWorkflow extends Task<Boolean> {
|
|||||||
|
|
||||||
private void handleGenericError(Throwable e) {
|
private void handleGenericError(Throwable e) {
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed for technical reasons.", e);
|
||||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
errorComponent.cause(e).window(window).build().showErrorScene();
|
||||||
}
|
|
||||||
|
|
||||||
private void wipePassword(char[] pw) {
|
|
||||||
if (pw != null) {
|
|
||||||
Arrays.fill(pw, ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void scheduled() {
|
|
||||||
vault.setState(VaultState.PROCESSING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
vault.setState(VaultState.UNLOCKED);
|
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayName());
|
||||||
|
|
||||||
|
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
|
||||||
|
case ASK -> Platform.runLater(() -> {
|
||||||
|
window.setScene(successScene.get());
|
||||||
|
window.show();
|
||||||
|
});
|
||||||
|
case REVEAL -> {
|
||||||
|
Platform.runLater(window::close);
|
||||||
|
vaultService.reveal(vault);
|
||||||
|
}
|
||||||
|
case IGNORE -> Platform.runLater(window::close);
|
||||||
|
}
|
||||||
|
|
||||||
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.UNLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failed() {
|
protected void failed() {
|
||||||
vault.setState(VaultState.LOCKED);
|
LOG.info("Unlock of '{}' failed.", vault.getDisplayName());
|
||||||
|
Throwable throwable = super.getException();
|
||||||
|
if (throwable instanceof InvalidMountPointException e) {
|
||||||
|
handleInvalidMountPoint(e);
|
||||||
|
} else {
|
||||||
|
handleGenericError(throwable);
|
||||||
|
}
|
||||||
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cancelled() {
|
protected void cancelled() {
|
||||||
vault.setState(VaultState.LOCKED);
|
LOG.debug("Unlock of '{}' canceled.", vault.getDisplayName());
|
||||||
|
vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
|
public enum SelectedVaultOptionsTab {
|
||||||
|
/**
|
||||||
|
* Let the controller decide which tab to show.
|
||||||
|
*/
|
||||||
|
ANY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show general tab
|
||||||
|
*/
|
||||||
|
GENERAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show mounting tab
|
||||||
|
*/
|
||||||
|
MOUNT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show password tab
|
||||||
|
*/
|
||||||
|
KEY,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import org.cryptomator.common.vaults.Vault;
|
|||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
@@ -25,7 +26,10 @@ public interface VaultOptionsComponent {
|
|||||||
@FxmlScene(FxmlFile.VAULT_OPTIONS)
|
@FxmlScene(FxmlFile.VAULT_OPTIONS)
|
||||||
Lazy<Scene> scene();
|
Lazy<Scene> scene();
|
||||||
|
|
||||||
default void showVaultOptionsWindow() {
|
ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty();
|
||||||
|
|
||||||
|
default void showVaultOptionsWindow(SelectedVaultOptionsTab selectedTab) {
|
||||||
|
selectedTabProperty().set(selectedTab);
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(scene().get());
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|||||||
@@ -1,13 +1,67 @@
|
|||||||
package org.cryptomator.ui.vaultoptions;
|
package org.cryptomator.ui.vaultoptions;
|
||||||
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TabPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
|
|
||||||
@VaultOptionsScoped
|
@VaultOptionsScoped
|
||||||
public class VaultOptionsController implements FxController {
|
public class VaultOptionsController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultOptionsController.class);
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty;
|
||||||
|
public TabPane tabPane;
|
||||||
|
public Tab generalTab;
|
||||||
|
public Tab mountTab;
|
||||||
|
public Tab keyTab;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultOptionsController() {}
|
VaultOptionsController(@VaultOptionsWindow Stage window, ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty) {
|
||||||
|
this.window = window;
|
||||||
|
this.selectedTabProperty = selectedTabProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
window.setOnShowing(this::windowWillAppear);
|
||||||
|
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||||
|
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectChosenTab() {
|
||||||
|
Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
|
||||||
|
tabPane.getSelectionModel().select(toBeSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tab getTabToSelect(SelectedVaultOptionsTab selectedTab) {
|
||||||
|
return switch (selectedTab) {
|
||||||
|
case ANY, GENERAL -> generalTab;
|
||||||
|
case MOUNT -> mountTab;
|
||||||
|
case KEY -> keyTab;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectedTabChanged() {
|
||||||
|
Tab selectedTab = tabPane.getSelectionModel().getSelectedItem();
|
||||||
|
try {
|
||||||
|
SelectedVaultOptionsTab selectedVaultOptionsTab = SelectedVaultOptionsTab.valueOf(selectedTab.getId());
|
||||||
|
selectedTabProperty.set(selectedVaultOptionsTab);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.error("Unknown vault options tab id: {}", selectedTab.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
|
||||||
|
selectChosenTab();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,18 @@ import dagger.multibindings.IntoMap;
|
|||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.StageFactory;
|
import org.cryptomator.ui.common.StageFactory;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||||
|
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -26,11 +28,17 @@ import java.util.ResourceBundle;
|
|||||||
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
|
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class})
|
||||||
abstract class VaultOptionsModule {
|
abstract class VaultOptionsModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@VaultOptionsScoped
|
||||||
|
static ObjectProperty<SelectedVaultOptionsTab> provideSelectedTabProperty() {
|
||||||
|
return new SimpleObjectProperty<>(SelectedVaultOptionsTab.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@VaultOptionsWindow
|
@VaultOptionsWindow
|
||||||
@VaultOptionsScoped
|
@VaultOptionsScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -50,8 +58,8 @@ abstract class VaultOptionsModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.VAULT_OPTIONS)
|
@FxmlScene(FxmlFile.VAULT_OPTIONS)
|
||||||
@VaultOptionsScoped
|
@VaultOptionsScoped
|
||||||
static Scene provideVaultOptionsScene(@VaultOptionsWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideVaultOptionsScene(@VaultOptionsWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/vault_options.fxml");
|
return fxmlLoaders.createScene(FxmlFile.VAULT_OPTIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
@@ -26,8 +26,8 @@ abstract class WrongFileAlertModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@WrongFileAlertWindow
|
@WrongFileAlertWindow
|
||||||
@WrongFileAlertScoped
|
@WrongFileAlertScoped
|
||||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -45,8 +45,8 @@ abstract class WrongFileAlertModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.WRONGFILEALERT)
|
@FxmlScene(FxmlFile.WRONGFILEALERT)
|
||||||
@WrongFileAlertScoped
|
@WrongFileAlertScoped
|
||||||
static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FxmlLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/wrongfilealert.fxml");
|
return fxmlLoaders.createScene(FxmlFile.WRONGFILEALERT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
alignment="CENTER_LEFT">
|
alignment="CENTER_LEFT">
|
||||||
<fx:define>
|
<fx:define>
|
||||||
<ToggleGroup fx:id="predefinedLocationToggler"/>
|
<ToggleGroup fx:id="predefinedLocationToggler"/>
|
||||||
|
<FontAwesome5IconView fx:id="badLocation" styleClass="glyph-icon-red" glyph="TIMES" />
|
||||||
|
<FontAwesome5IconView fx:id="goodLocation" styleClass="glyph-icon-primary" glyph="CHECK" />
|
||||||
</fx:define>
|
</fx:define>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="24"/>
|
<Insets topRightBottomLeft="24"/>
|
||||||
@@ -33,6 +35,8 @@
|
|||||||
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
|
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
|
||||||
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
|
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
|
||||||
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.locationPresets.foundOnedrive}" managed="${controller.locationPresets.foundOnedrive}"/>
|
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.locationPresets.foundOnedrive}" managed="${controller.locationPresets.foundOnedrive}"/>
|
||||||
|
<RadioButton fx:id="megaRadioButton" toggleGroup="${predefinedLocationToggler}" text="MEGA" visible="${controller.locationPresets.foundMega}" managed="${controller.locationPresets.foundMega}"/>
|
||||||
|
<RadioButton fx:id="pcloudRadioButton" toggleGroup="${predefinedLocationToggler}" text="pCloud" visible="${controller.locationPresets.foundPcloud}" managed="${controller.locationPresets.foundPcloud}"/>
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||||
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
||||||
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
||||||
@@ -47,12 +51,8 @@
|
|||||||
|
|
||||||
<VBox spacing="6">
|
<VBox spacing="6">
|
||||||
<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
|
<Label text="%addvaultwizard.new.locationLabel" labelFor="$locationTextField"/>
|
||||||
<TextField fx:id="locationTextField" promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" disable="true" HBox.hgrow="ALWAYS"/>
|
<TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" editable="false" disable="${!controller.anyRadioButtonSelected}" HBox.hgrow="ALWAYS"/>
|
||||||
<Label text="${controller.warningText}" wrapText="true" visible="${controller.showWarning}">
|
<Label fx:id="vaultPathStatus" styleClass="label-muted" alignment="CENTER_RIGHT" wrapText="true" visible="${controller.anyRadioButtonSelected}" maxWidth="Infinity" graphicTextGap="6" text="${controller.statusText}" graphic="${controller.statusGraphic}" />
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
|
|
||||||
</graphic>
|
|
||||||
</Label>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|||||||
@@ -38,12 +38,12 @@
|
|||||||
<fx:include source="/fxml/preferences_updates.fxml"/>
|
<fx:include source="/fxml/preferences_updates.fxml"/>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab fx:id="donationKeyTab" id="DONATION_KEY" text="%preferences.donationKey">
|
<Tab fx:id="contributeTab" id="CONTRIBUTE" text="%preferences.contribute">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5IconView glyph="HEART"/>
|
<FontAwesome5IconView glyph="HEART"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
<content>
|
<content>
|
||||||
<fx:include source="/fxml/preferences_donationkey.fxml"/>
|
<fx:include source="/fxml/preferences_contribute.fxml"/>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab fx:id="aboutTab" id="ABOUT" text="%preferences.about">
|
<Tab fx:id="aboutTab" id="ABOUT" text="%preferences.about">
|
||||||
|
|||||||
+5
-5
@@ -12,7 +12,7 @@
|
|||||||
<?import javafx.scene.shape.Circle?>
|
<?import javafx.scene.shape.Circle?>
|
||||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns="http://javafx.com/javafx"
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="org.cryptomator.ui.preferences.DonationKeyPreferencesController"
|
fx:controller="org.cryptomator.ui.preferences.SupporterCertificateController"
|
||||||
spacing="18">
|
spacing="18">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="12"/>
|
<Insets topRightBottomLeft="12"/>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
|
<FormattedLabel format="%preferences.contribute.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
<VBox HBox.hgrow="ALWAYS" spacing="6">
|
<VBox HBox.hgrow="ALWAYS" spacing="6">
|
||||||
<Label text="%preferences.donationKey.noDonationKey" wrapText="true" VBox.vgrow="ALWAYS"/>
|
<Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS"/>
|
||||||
<Hyperlink text="%preferences.donationKey.getDonationKey" onAction="#getDonationKey" contentDisplay="LEFT">
|
<Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate" contentDisplay="LEFT">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontAwesome5IconView glyph="LINK"/>
|
<FontAwesome5IconView glyph="LINK"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -43,6 +43,6 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|
||||||
<TextArea fx:id="donationKeyField" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
|
<TextArea fx:id="supporterCertificateField" promptText="%preferences.contribute.promptText" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<Label text="%preferences.general.theme"/>
|
<Label text="%preferences.general.theme"/>
|
||||||
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
|
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
|
||||||
<Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.general.unlockThemes" onAction="#showDonationTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/>
|
<Hyperlink styleClass="hyperlink-underline,hyperlink-muted" text="%preferences.general.unlockThemes" onAction="#showContributeTab" visible="${!controller.licenseHolder.validLicense}" managed="${!controller.licenseHolder.validLicense}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user