mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-21 15:34:27 +00:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6afb5fd6 | ||
|
|
d2b1f86d85 | ||
|
|
b01478f0fa | ||
|
|
27eb6298ae | ||
|
|
2c53afd70f | ||
|
|
ae84f2f2fd | ||
|
|
ad17f0b835 | ||
|
|
e7db172a89 | ||
|
|
222e1f18ad | ||
|
|
9a3baf7119 | ||
|
|
53baf3c648 | ||
|
|
c8a6b7cf13 | ||
|
|
5d12a62e38 | ||
|
|
40bb0079ac | ||
|
|
fbdf511f05 | ||
|
|
410900ed51 | ||
|
|
1302c1fc34 | ||
|
|
ea806ec97a | ||
|
|
77dbf9414a | ||
|
|
a3fc50fc7e | ||
|
|
4b0e8b5841 | ||
|
|
a428ae6260 | ||
|
|
9c41e348fa |
@@ -48,9 +48,6 @@ on:
|
||||
- 'dist/linux/common/**'
|
||||
- 'dist/linux/resources/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '26.0.1+8'
|
||||
|
||||
@@ -9,9 +9,6 @@ on:
|
||||
description: 'Source or Release tag'
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
|
||||
@@ -34,8 +34,6 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
download-file:
|
||||
name: Downloads the file into the VM
|
||||
|
||||
@@ -17,10 +17,6 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Compile and Test
|
||||
|
||||
@@ -15,8 +15,6 @@ env:
|
||||
}
|
||||
}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
name: Checkout latest jdk version
|
||||
|
||||
@@ -32,9 +32,6 @@ env:
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25.0.3/openjfx-25.0.3_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64_HASH: 'e3fd682354346845d2944a2da2b1ff2b6cb9259d92027f2f9c121b9b93c5e42f'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
|
||||
@@ -5,9 +5,6 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-dependencies:
|
||||
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@8356563bf7b8d1c8d693f75ca487e8f57573cec9 # v3.1.0
|
||||
|
||||
@@ -4,9 +4,6 @@ on:
|
||||
schedule:
|
||||
- cron: '0/15 * * * *' # run every 15 min - don't forget to adjust the "interval" in the json sent to the metrics endpoint
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
report-download-stats:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -16,8 +16,6 @@ defaults:
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
version: ''
|
||||
|
||||
@@ -25,9 +23,6 @@ jobs:
|
||||
name: Compile and Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: get-version
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
if: needs.get-version.outputs.versionType != 'unknown'
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
@@ -76,8 +71,6 @@ jobs:
|
||||
build-exe-and-msi:
|
||||
needs: [get-version, create-release-draft]
|
||||
uses: ./.github/workflows/win-exe.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
semVerNum: ${{needs.get-version.outputs.semVerNum}}
|
||||
revisionNum: ${{needs.get-version.outputs.revNum}}
|
||||
@@ -87,8 +80,6 @@ jobs:
|
||||
build-dmg-arm64:
|
||||
needs: [get-version, create-release-draft]
|
||||
uses: ./.github/workflows/mac-dmg.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
semVerNum: ${{needs.get-version.outputs.semVerNum}}
|
||||
revisionNum: ${{needs.get-version.outputs.revNum}}
|
||||
@@ -98,8 +89,6 @@ jobs:
|
||||
build-dmg-x64:
|
||||
needs: [get-version, create-release-draft]
|
||||
uses: ./.github/workflows/mac-dmg-x64.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
semVerNum: ${{needs.get-version.outputs.semVerNum}}
|
||||
revisionNum: ${{needs.get-version.outputs.revNum}}
|
||||
@@ -109,8 +98,6 @@ jobs:
|
||||
build-appimages:
|
||||
needs: [get-version, create-release-draft]
|
||||
uses: ./.github/workflows/appimage.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
semVerNum: ${{needs.get-version.outputs.semVerNum}}
|
||||
revisionNum: ${{needs.get-version.outputs.revNum}}
|
||||
@@ -120,8 +107,6 @@ jobs:
|
||||
update-sha256sums:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-version, build-exe-and-msi, build-dmg-arm64, build-dmg-x64, build-appimages]
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
SEMVER: ${{ needs.get-version.outputs.semVerStr }}
|
||||
|
||||
@@ -6,9 +6,6 @@ on:
|
||||
discussion_comment:
|
||||
types: [created, edited, deleted]
|
||||
|
||||
permissions:
|
||||
discussions: read
|
||||
|
||||
jobs:
|
||||
update-error-db:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -28,9 +28,6 @@ env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 26
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
determine-version:
|
||||
name: 'Determines the version following semver'
|
||||
|
||||
@@ -23,9 +23,6 @@ on:
|
||||
- 'dist/linux/common/**'
|
||||
- 'dist/linux/resources/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
|
||||
@@ -23,9 +23,6 @@ on:
|
||||
- 'dist/linux/common/**'
|
||||
- 'dist/linux/resources/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
|
||||
@@ -62,9 +62,6 @@ env:
|
||||
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||
@@ -195,6 +192,7 @@ jobs:
|
||||
-Dlicense.includedScopes=compile
|
||||
-Dlicense.excludedGroups=^org\.cryptomator
|
||||
-Dlicense.failOnMissing=true
|
||||
-Dlicense.licenseMergesUrl=file://${{ github.workspace }}/license/merges
|
||||
- name: Install codesign certificate
|
||||
run: |
|
||||
# create variables
|
||||
|
||||
@@ -60,9 +60,6 @@ env:
|
||||
VERSION_SUFFIX: ${{ inputs.semVerSuffix || ''}}
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||
@@ -194,6 +191,7 @@ jobs:
|
||||
-Dlicense.includedScopes=compile
|
||||
-Dlicense.excludedGroups=^org\.cryptomator
|
||||
-Dlicense.failOnMissing=true
|
||||
-Dlicense.licenseMergesUrl=file://${{ github.workspace }}/license/merges
|
||||
- name: Install codesign certificate
|
||||
run: |
|
||||
# create variables
|
||||
|
||||
@@ -4,8 +4,6 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -144,3 +142,4 @@ jobs:
|
||||
token: ${{ secrets.CRYPTOBOT_WORKFLOW_DISPATCH_TOKEN }}
|
||||
repository: cryptomator/docs
|
||||
client-payload: '{ "version": "${{ github.event.release.tag_name }}", "release": ${{ toJson(github.event.release.assets) }} }'
|
||||
|
||||
|
||||
@@ -11,9 +11,6 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Compile and Test
|
||||
|
||||
@@ -14,9 +14,6 @@ env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 26
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-preconditions:
|
||||
name: Validate commits pushed to release/hotfix branch to fulfill release requirements
|
||||
|
||||
@@ -71,9 +71,6 @@ defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-msi:
|
||||
name: Build .msi Installer
|
||||
@@ -272,6 +269,7 @@ jobs:
|
||||
"-Dlicense.includedScopes=compile"
|
||||
"-Dlicense.excludedGroups=^org\.cryptomator"
|
||||
"-Dlicense.failOnMissing=true"
|
||||
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
|
||||
shell: pwsh
|
||||
- name: Create file association file from template
|
||||
working-directory: dist/win
|
||||
@@ -386,6 +384,7 @@ jobs:
|
||||
"-Dlicense.includedScopes=compile"
|
||||
"-Dlicense.excludedGroups=^org\.cryptomator"
|
||||
"-Dlicense.failOnMissing=true"
|
||||
"-Dlicense.licenseMergesUrl=file:///${{ github.workspace }}/license/merges"
|
||||
shell: pwsh
|
||||
- name: Download WinFsp
|
||||
run: |
|
||||
|
||||
@@ -7,8 +7,6 @@ on:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
winget:
|
||||
name: Publish winget package
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="25" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="temurin-26" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
+2
-15
@@ -9,17 +9,8 @@ Changes to prior versions can be found on the [Github release page](https://gith
|
||||
|
||||
|
||||
## [Unreleased](https://github.com/cryptomator/cryptomator/compare/1.19.3...HEAD)
|
||||
### Fixed
|
||||
* Fixed "Locate encrypted file" opening the file instead of revealing it in the file manager on Linux ([#4272](https://github.com/cryptomator/cryptomator/issues/4272))
|
||||
|
||||
### Changed
|
||||
* Improved usability of the trust on first use dialog ([#4295](https://github.com/cryptomator/cryptomator/pull/4295))
|
||||
* Migrated code to common JDK 25 APIs and constructs
|
||||
* Added Maven profile to run the app from the command line ([#4317](https://github.com/cryptomator/cryptomator/pull/4317))
|
||||
* Updated dependencies:
|
||||
- `org.cryptomator:integrations-linux` from 1.7.0 to 1.8.0-beta1
|
||||
- `com.fasterxml.jackson.core:jackson-databind` from 2.21.4 to 2.21.6
|
||||
- `com.fasterxml.jackson.datatype:jackson-datatype-jsr310` from 2.21.4 to 2.21.6
|
||||
No changes yet.
|
||||
|
||||
|
||||
## [1.19.3](https://github.com/cryptomator/cryptomator/releases/1.19.3) - 2026-06-29
|
||||
@@ -28,8 +19,7 @@ Changes to prior versions can be found on the [Github release page](https://gith
|
||||
|
||||
### Fixed
|
||||
* Fixed Cryptomator file extensions were not registered on Windows ([#4219](https://github.com/cryptomator/cryptomator/issues/4219))
|
||||
* Fixed warning was displayed when accessing update tab in settings even though an update check did not ran ([#4194](https://github.com/cryptomator/cryptomator/pull/4194))
|
||||
* Fixed the same vault could be added several times to the vault list ([#4243](https://github.com/cryptomator/cryptomator/pull/4243))
|
||||
* Fixed warning was displayed when accessing update tab in settings even though an update check did not ran ([#4199](https://github.com/cryptomator/cryptomator/pull/4199))
|
||||
* Fixed several Decrypt Name dialogs could be opened on the same vault ([#4164](https://github.com/cryptomator/cryptomator/pull/4164))
|
||||
* Fixed not all mount options in vault specific settings could be displayed ([#4227](https://github.com/cryptomator/cryptomator/pull/4227))
|
||||
* Fixed localhost alias on Windows was not removed on uninstall ([#3993](https://github.com/cryptomator/cryptomator/issues/3993))
|
||||
@@ -41,9 +31,6 @@ Changes to prior versions can be found on the [Github release page](https://gith
|
||||
* Drop signing with Actalis issued certificate ([#4169](https://github.com/cryptomator/cryptomator/pull/4169), [#4262](https://github.com/cryptomator/cryptomator/pull/4262))
|
||||
* Fix dagger binding graph issues ([#4147](https://github.com/cryptomator/cryptomator/pull/4147))
|
||||
* Added flatpak build to CI ([#4199](https://github.com/cryptomator/cryptomator/pull/4199))
|
||||
* Added monthly distribution builds to CI ([#4187](https://github.com/cryptomator/cryptomator/pull/4187))
|
||||
* Replaced Guava cache with Caffeine
|
||||
* Updated translations ([#4223](https://github.com/cryptomator/cryptomator/pull/4223))
|
||||
* Updated dependencies:
|
||||
- `org.cryptomator:webdav-nio-adapter` from 3.0.1 to 3.0.2
|
||||
- `org.cryptomator:integrations-api` from 1.8.0 to 1.9.0
|
||||
|
||||
@@ -89,20 +89,6 @@ For more information on the security details visit [cryptomator.org](https://doc
|
||||
|
||||
This will build all the jars and bundle them together with their OS-specific dependencies under `target`. This can now be used to build native packages.
|
||||
|
||||
### Running the app
|
||||
|
||||
```
|
||||
./mvnw -Prun compile exec:exec
|
||||
```
|
||||
|
||||
This starts the app with the defaults defined in `pom.xml`. The OS-specific parameters are picked automatically.
|
||||
|
||||
Add the `dev` profile to use the `Cryptomator-Dev` settings, log and mount directories instead, so a development run does not interfere with an installed Cryptomator:
|
||||
|
||||
```
|
||||
./mvnw -Prun,dev compile exec:exec
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
||||
|
||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
||||
# This is the Cryptomator administrative configuration file.
|
||||
# It is a simple key-value pair file.
|
||||
# Lines starting with '#' are comments and will be ignored.
|
||||
# For more info, read the docs at https://docs.cryptomator.org/desktop/admin-config/
|
||||
# For more info, read the docs at https://docs.cryptomator.org/desktop/advanced-settings/
|
||||
#
|
||||
# Example:
|
||||
# Sets the plugin directory and enables plugin loading
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Name=Cryptomator
|
||||
Comment=Cloud Storage Encryption Utility
|
||||
Exec=cryptomator %F
|
||||
Exec=cryptomator %U
|
||||
Icon=org.cryptomator.Cryptomator
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Security;FileTools;
|
||||
StartupNotify=true
|
||||
StartupWMClass=org.cryptomator.launcher.Cryptomator$MainApp
|
||||
MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault;
|
||||
MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault;x-scheme-handler/org.cryptomator;
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ pkgdesc="Multiplatform transparent client-side encryption of your files in the c
|
||||
arch=('any')
|
||||
url="https://cryptomator.org/"
|
||||
license=('GPL3')
|
||||
depends=('fuse3' 'alsa-lib' 'hicolor-icon-theme' 'libxtst' 'libnet' 'libxrender')
|
||||
depends=('fuse3' 'alsa-lib' 'hicolor-icon-theme' 'libxtst' 'libnet' 'libxrender' 'desktop-file-utils')
|
||||
makedepends=('maven' 'unzip')
|
||||
optdepends=('keepassxc-cryptomator: Use KeePassXC to store vault passwords' 'ttf-hanazono: Install this font when using Japanese system language')
|
||||
_jdkver=26.0.1+8
|
||||
|
||||
Vendored
+2
-1
@@ -143,7 +143,8 @@ cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
|
||||
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \
|
||||
-Dlicense.includedScopes=compile \
|
||||
-Dlicense.excludedGroups=^org\.cryptomator \
|
||||
-Dlicense.failOnMissing=true
|
||||
-Dlicense.failOnMissing=true \
|
||||
-Dlicense.licenseMergesUrl=file://$(pwd)/../../../license/merges
|
||||
|
||||
# codesign
|
||||
if [ -n "${CODESIGN_IDENTITY}" ] && [ -n "${TEAM_IDENTIFIER}" ]; then
|
||||
|
||||
Vendored
+14
@@ -46,6 +46,20 @@
|
||||
<string>Any</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<!-- register org.cryptomator:// URL scheme -->
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>org.cryptomator.deeplink</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>org.cryptomator</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<!-- register .cryptomator extension -->
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
|
||||
@@ -9,3 +9,4 @@ installer
|
||||
*.jmod
|
||||
resources/jfxJmods.zip
|
||||
license.rtf
|
||||
**/FAvaultFile.properties
|
||||
Vendored
+13
-6
@@ -40,6 +40,11 @@ if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null)
|
||||
Write-Error "Unable to find git.exe in your PATH (try: choco install git)"
|
||||
exit 1
|
||||
}
|
||||
if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null)
|
||||
{
|
||||
Write-Error "Unable to find mvn.cmd in your PATH (try: choco install maven)"
|
||||
exit 1
|
||||
}
|
||||
if ((Get-Command 'wix' -ErrorAction SilentlyContinue) -eq $null)
|
||||
{
|
||||
Write-Error 'Unable to find wix in your PATH (try: dotnet tool install --global wix --version 6.0.2)'
|
||||
@@ -60,7 +65,7 @@ if ($wixExtensions -notmatch 'WixToolset.BootstrapperApplications.wixext') {
|
||||
}
|
||||
|
||||
$buildDir = Split-Path -Parent $PSCommandPath
|
||||
$version = $(../../mvnw.cmd -f $buildDir/../../pom.xml help:evaluate -Dexpression="project.version" -q -DforceStdout)
|
||||
$version = $(mvn -f $buildDir/../../pom.xml help:evaluate -Dexpression="project.version" -q -DforceStdout)
|
||||
$semVerNo = $version -replace '(\d+\.\d+\.\d+).*','$1'
|
||||
$revisionNo = $(git rev-list --count HEAD)
|
||||
|
||||
@@ -74,7 +79,7 @@ $copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
|
||||
|
||||
# compile
|
||||
Invoke-CommandWithExitCheck -Command `
|
||||
"../../mvnw.cmd" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "clean", "package", "-DskipTests", "-Pwin")
|
||||
"mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "clean", "package", "-DskipTests", "-Pwin")
|
||||
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
|
||||
|
||||
# add runtime
|
||||
@@ -207,13 +212,14 @@ if ($LASTEXITCODE -ne 0) {
|
||||
|
||||
#Create RTF license for msi
|
||||
Invoke-CommandWithExitCheck -Command `
|
||||
"../../mvnw.cmd" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
|
||||
"mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
|
||||
"-Dlicense.thirdPartyFilename=license.rtf", `
|
||||
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl", `
|
||||
"-Dlicense.outputDirectory=$buildDir\resources\", `
|
||||
"-Dlicense.includedScopes=compile", `
|
||||
"-Dlicense.excludedGroups=^org\.cryptomator", `
|
||||
"-Dlicense.failOnMissing=true")
|
||||
"-Dlicense.failOnMissing=true", `
|
||||
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges")
|
||||
|
||||
# patch app dir
|
||||
Copy-Item "contrib\*" -Destination "$AppName"
|
||||
@@ -253,13 +259,14 @@ Invoke-CommandWithExitCheck -Command `
|
||||
|
||||
#Create RTF license for bundle
|
||||
Invoke-CommandWithExitCheck -Command `
|
||||
"../../mvnw.cmd" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
|
||||
"mvn" -Arguments @("-B", "-f", "$buildDir/../../pom.xml", "license:add-third-party", `
|
||||
"-Dlicense.thirdPartyFilename=license.rtf", `
|
||||
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl", `
|
||||
"-Dlicense.outputDirectory=$buildDir\bundle\resources\", `
|
||||
"-Dlicense.includedScopes=compile", `
|
||||
"-Dlicense.excludedGroups=^org\.cryptomator", `
|
||||
"-Dlicense.failOnMissing=true")
|
||||
"-Dlicense.failOnMissing=true", `
|
||||
"-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges")
|
||||
|
||||
# download Winfsp
|
||||
$winfspMsiUrl= 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi'
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
mime-type=application/vnd.cryptomator.vault
|
||||
extension=cryptomator
|
||||
description=Cryptomator Vault File
|
||||
icon=C:\\Users\\Arbeit\\Skymatic\\cryptomator-jdk26-jpackage\\dist\\win\\resources\\Cryptomator-Vault.ico
|
||||
Vendored
+15
@@ -27,6 +27,7 @@
|
||||
<?define ProgIdContentType= "application/vnd.cryptomator.encrypted" ?>
|
||||
<?define CloseApplicationTarget= "cryptomator.exe" ?>
|
||||
<?define LoopbackAlias= "cryptomator-vault" ?>
|
||||
<?define UrlProtocolScheme= "org.cryptomator" ?>
|
||||
|
||||
<?include $(var.JpConfigDir)/overrides.wxi ?>
|
||||
|
||||
@@ -97,6 +98,19 @@
|
||||
<ns0:Extension Id="c9u" Advertise="no" ContentType="$(var.ProgIdContentType)"/>
|
||||
</ns0:ProgId>
|
||||
</ns0:Component>
|
||||
<!-- Register "org.cryptomator://" URL protocol handler -->
|
||||
<ns0:Component Bitness="always64" Id="UrlProtocolHandler" Guid="*">
|
||||
<ns0:RegistryKey Root="HKMU" Key="Software\Classes\$(var.UrlProtocolScheme)">
|
||||
<ns0:RegistryValue Type="string" Value="URL:$(var.JpAppName) Protocol" KeyPath="yes"/>
|
||||
<ns0:RegistryValue Name="URL Protocol" Type="string" Value=""/>
|
||||
<ns0:RegistryKey Key="DefaultIcon">
|
||||
<ns0:RegistryValue Type="string" Value="[INSTALLDIR]$(var.JpAppName).exe,0"/>
|
||||
</ns0:RegistryKey>
|
||||
<ns0:RegistryKey Key="shell\open\command">
|
||||
<ns0:RegistryValue Type="string" Value=""[INSTALLDIR]$(var.JpAppName).exe" "%1""/>
|
||||
</ns0:RegistryKey>
|
||||
</ns0:RegistryKey>
|
||||
</ns0:Component>
|
||||
</ns0:DirectoryRef>
|
||||
|
||||
<ns0:StandardDirectory Id="CommonAppDataFolder">
|
||||
@@ -126,6 +140,7 @@
|
||||
<ns0:ComponentGroupRef Id="FileAssociations"/>
|
||||
<!-- Ref to additional ProgIDs -->
|
||||
<ns0:ComponentRef Id="nonStartingProgID"/>
|
||||
<ns0:ComponentRef Id="UrlProtocolHandler"/>
|
||||
<ns0:ComponentRef Id="AdminConfigDir"/>
|
||||
<ns0:ComponentRef Id="AdminConfigFile"/>
|
||||
</ns0:Feature>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
Apache License v2.0|Apache License, Version 2.0|The Apache License, Version 2.0|The Apache Software License, Version 2.0|Apache 2.0|Apache Software License - Version 2.0|Apache-2.0
|
||||
MIT License|MIT|The MIT License (MIT)|The MIT License|MIT license
|
||||
LGPL 2.1|LGPL, version 2.1|GNU Lesser/Library General Public License version 2|GNU Lesser General Public License Version 2.1|GNU Lesser General Public License|LGPL-2.1-only
|
||||
LGPL 2.1|LGPL, version 2.1|GNU Lesser/Library General Public License version 2|GNU Lesser General Public License Version 2.1|GNU Lesser General Public License
|
||||
GPLv2|GNU General Public License Version 2
|
||||
GPLv2+CE|CDDL + GPLv2 with classpath exception
|
||||
Eclipse Public License - Version 1.0|Eclipse Public License - v 1.0
|
||||
Eclipse Public License - Version 2.0|Eclipse Public License - v 2.0|EPL-2.0
|
||||
Eclipse Public License - Version 2.0|Eclipse Public License - v 2.0
|
||||
@@ -33,12 +33,12 @@
|
||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.10.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>2.11.0-SNAPSHOT</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptolib.version>2.2.2</cryptomator.cryptolib.version>
|
||||
<cryptomator.integrations.version>1.9.0</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.6.1</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.5.0</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.8.0-beta1</cryptomator.integrations.linux.version>
|
||||
<cryptomator.integrations.linux.version>1.7.0</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>6.0.1</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>3.0.2</cryptomator.webdav.version>
|
||||
<cryptomator.webdav-servlet.version>1.2.12</cryptomator.webdav-servlet.version>
|
||||
@@ -48,7 +48,7 @@
|
||||
<commons-lang3.version>3.20.0</commons-lang3.version>
|
||||
<dagger.version>2.59.2</dagger.version>
|
||||
<easybind.version>2.2</easybind.version>
|
||||
<jackson.version>2.21.6</jackson.version>
|
||||
<jackson.version>2.21.4</jackson.version>
|
||||
<javafx.version>25.0.3</javafx.version>
|
||||
<jwt.version>4.5.2</jwt.version>
|
||||
<nimbus-jose.version>10.5</nimbus-jose.version>
|
||||
@@ -72,16 +72,9 @@
|
||||
<mvn-dependency.version>3.11.0</mvn-dependency.version>
|
||||
<mvn-surefire.version>3.5.6</mvn-surefire.version>
|
||||
<mvn-jar.version>3.5.0</mvn-jar.version>
|
||||
<mvn-exec.version>3.5.1</mvn-exec.version>
|
||||
|
||||
<!-- Property used by surefire to determine jacoco engine -->
|
||||
<surefire.jacoco.args></surefire.jacoco.args>
|
||||
|
||||
<!-- App runtime config used by the 'run' profile, mirroring .idea/runConfigurations. The 'dev' profile switches these to the Cryptomator-Dev variants. -->
|
||||
<run.appDir>Cryptomator</run.appDir>
|
||||
<run.linuxFuseArgs></run.linuxFuseArgs>
|
||||
<run.macMountPointsDir>@{userhome}/Cryptomator</run.macMountPointsDir>
|
||||
<run.macUpdateArgs>-Dcryptomator.updateMechanism=org.cryptomator.macos.update.DmgUpdateMechanism</run.macUpdateArgs>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -306,10 +299,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${mvn-surefire.version}</version>
|
||||
<configuration>
|
||||
<!--suppress MavenModelInspection -->
|
||||
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
@@ -331,11 +320,6 @@
|
||||
<artifactId>dependency-check-maven</artifactId>
|
||||
<version>${dependency-check.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>${mvn-exec.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
@@ -415,20 +399,11 @@
|
||||
<includeGroupIds>${nonModularGroupIds}</includeGroupIds>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>get-mockito-agent-path</id>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<licenseMergesUrl>${project.baseUri}license/merges</licenseMergesUrl>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-third-party</id>
|
||||
@@ -440,6 +415,7 @@
|
||||
<thirdPartyFilename>THIRD-PARTY.txt</thirdPartyFilename>
|
||||
<includedScopes>compile</includedScopes>
|
||||
<excludedGroups>org\.cryptomator</excludedGroups>
|
||||
<licenseMergesUrl>file:///${project.basedir}/license/merges</licenseMergesUrl>
|
||||
<fileTemplate>${project.basedir}/src/main/resources/license/template.ftl</fileTemplate>
|
||||
</configuration>
|
||||
</execution>
|
||||
@@ -611,102 +587,6 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
|
||||
<!-- Launches the app from the command line: ./mvnw -Prun compile exec:exec (append ,dev for the Cryptomator-Dev variant) -->
|
||||
<profile>
|
||||
<id>run</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<!-- same split into module path and class path as the copy-mods/copy-libs executions used for packaging -->
|
||||
<execution>
|
||||
<id>build-run-modulepath</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>build-classpath</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<excludeGroupIds>${nonModularGroupIds}</excludeGroupIds>
|
||||
<outputProperty>run.modulePath</outputProperty>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>build-run-classpath</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>build-classpath</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<includeGroupIds>${nonModularGroupIds}</includeGroupIds>
|
||||
<outputProperty>run.classPath</outputProperty>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<executable>${java.home}/bin/java</executable>
|
||||
<commandlineArgs>--module-path "${project.build.outputDirectory}${path.separator}${run.modulePath}" --class-path "${run.classPath}" ${run.jvmArgs} --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator</commandlineArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<!-- Switches the 'run' profile to the Cryptomator-Dev variants, so a dev run does not touch the settings of an installed Cryptomator -->
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<properties>
|
||||
<run.appDir>Cryptomator-Dev</run.appDir>
|
||||
<run.linuxFuseArgs>-Dfuse.experimental="true"</run.linuxFuseArgs>
|
||||
<run.macMountPointsDir>@{userhome}/Library/Application Support/Cryptomator-Dev/mnt</run.macMountPointsDir>
|
||||
<run.macUpdateArgs></run.macUpdateArgs>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>run-win</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>windows</family>
|
||||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<run.jvmArgs>-Dcryptomator.settingsPath="@{appdata}/${run.appDir}/settings.json;@{userhome}/AppData/Roaming/${run.appDir}/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/${run.appDir}/ipc.socket" -Dcryptomator.logDir="@{localappdata}/${run.appDir}" -Dcryptomator.pluginDir="@{appdata}/${run.appDir}/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/${run.appDir}/keychain.json;@{userhome}/AppData/Roaming/${run.appDir}/keychain.json" -Dcryptomator.integrationsWin.windowsHelloKeychainPaths="@{appdata}/${run.appDir}/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/${run.appDir}/windowsHelloKeychain.json" -Dcryptomator.p12Path="@{appdata}/${run.appDir}/key.p12;@{userhome}/AppData/Roaming/${run.appDir}/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/${run.appDir}" -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win,javafx.graphics</run.jvmArgs>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>run-mac</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>mac</family>
|
||||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<run.jvmArgs>-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/${run.appDir}/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/${run.appDir}/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/${run.appDir}/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/${run.appDir}" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/${run.appDir}/Plugins" -Dcryptomator.mountPointsDir="${run.macMountPointsDir}" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator ${run.macUpdateArgs} -Dcryptomator.hub.enableTrustOnFirstUse=true -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac,javafx.graphics</run.jvmArgs>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>run-linux</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>unix</family>
|
||||
<name>linux</name>
|
||||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<run.jvmArgs>-Dcryptomator.settingsPath="@{userhome}/.config/${run.appDir}/settings.json" -Dcryptomator.p12Path="@{userhome}/.config/${run.appDir}/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/.config/${run.appDir}/ipc.socket" -Dcryptomator.logDir="@{userhome}/.local/share/${run.appDir}/logs" -Dcryptomator.pluginDir="@{userhome}/.local/share/${run.appDir}/plugins" -Dcryptomator.mountPointsDir="@{userhome}/.local/share/${run.appDir}/mnt" -Dcryptomator.showTrayIcon=true -Dcryptomator.hub.enableTrustOnFirstUse=true ${run.linuxFuseArgs} -Xss20m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator,javafx.graphics</run.jvmArgs>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -165,7 +166,7 @@ public class Environment {
|
||||
|
||||
private Optional<Path> getPath(String propertyName) {
|
||||
String value = System.getProperty(propertyName);
|
||||
return Optional.ofNullable(value).map(Path::of);
|
||||
return Optional.ofNullable(value).map(Paths::get);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ErrorCode {
|
||||
public static ErrorCode of(Throwable throwable) {
|
||||
var causalChain = Throwables.getCausalChain(throwable);
|
||||
if (causalChain.size() > 1) {
|
||||
var rootCause = causalChain.getLast();
|
||||
var rootCause = causalChain.get(causalChain.size() - 1);
|
||||
var parentOfRootCause = causalChain.get(causalChain.size() - 2);
|
||||
var rootSpecificFrames = countTopmostFrames(rootCause.getStackTrace(), parentOfRootCause.getStackTrace());
|
||||
return new ErrorCode(throwable, rootCause, rootSpecificFrames);
|
||||
|
||||
@@ -67,7 +67,7 @@ public class SubstitutingProperties extends PropertiesDecorator {
|
||||
|
||||
private enum Source {
|
||||
ENV,
|
||||
PROPS
|
||||
PROPS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -8,9 +8,9 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -78,7 +78,7 @@ public final class OneDriveWindowsLocationPresetsProvider implements LocationPre
|
||||
*/
|
||||
@Blocking
|
||||
private static void waitForSuccess(Process process, int timeoutSeconds, String cmdDescription) throws TimeoutException, InterruptedException, CommandFailedException {
|
||||
boolean exited = process.waitFor(Duration.ofSeconds(timeoutSeconds));
|
||||
boolean exited = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (!exited) {
|
||||
throw new TimeoutException(cmdDescription + " timed out after " + timeoutSeconds + "s");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public final class MountWithinParentUtil {
|
||||
|
||||
EMPTY_DIR,
|
||||
|
||||
BROKEN_JUNCTION
|
||||
BROKEN_JUNCTION;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
@@ -61,7 +62,7 @@ public class VaultSettings {
|
||||
|
||||
VaultSettings(VaultSettingsJson json) {
|
||||
this.id = json.id;
|
||||
this.path = new SimpleObjectProperty<>(this, "path", json.path == null ? null : Path.of(json.path));
|
||||
this.path = new SimpleObjectProperty<>(this, "path", json.path == null ? null : Paths.get(json.path));
|
||||
this.displayName = new SimpleStringProperty(this, "displayName", json.displayName);
|
||||
this.unlockAfterStartup = new SimpleBooleanProperty(this, "unlockAfterStartup", json.unlockAfterStartup);
|
||||
this.revealAfterMount = new SimpleBooleanProperty(this, "revealAfterMount", json.revealAfterMount);
|
||||
|
||||
@@ -47,6 +47,7 @@ import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.ReadOnlyFileSystemException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
@@ -57,7 +58,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
public class Vault {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
private static final Path HOME_DIR = Path.of(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;
|
||||
|
||||
@@ -58,7 +58,7 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
||||
/**
|
||||
* Unknown state due to preceding unrecoverable exceptions.
|
||||
*/
|
||||
ERROR
|
||||
ERROR;
|
||||
}
|
||||
|
||||
private final AtomicReference<Value> value;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
public record AppLaunchEvent(AppLaunchEvent.EventType type, Collection<Path> pathsToOpen) {
|
||||
|
||||
public enum EventType {
|
||||
REVEAL_APP,
|
||||
OPEN_FILE
|
||||
}
|
||||
/**
|
||||
* An event triggering an action in the running application instance.
|
||||
* <p>
|
||||
* Produced by the launch-argument handling (see {@link LaunchArgsParser} and the {@code *RequestHandler}s) and consumed
|
||||
* by the UI's {@code AppLaunchEventHandler}. Each permitted subtype represents one supported action:
|
||||
* <ul>
|
||||
* <li>{@link RevealRunningEvent} - reveal the already-running app,</li>
|
||||
* <li>{@link OpenFileEvent} - open one or more paths,</li>
|
||||
* <li>{@link OpenHubVaultEvent} - open a Hub vault from a deeplink.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public sealed interface AppLaunchEvent permits RevealRunningEvent, OpenFileEvent, OpenHubVaultEvent {
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class CryptomatorModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("launchEventQueue")
|
||||
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
|
||||
static BlockingQueue<AppLaunchEvent> provideLaunchEventQueue() {
|
||||
return new ArrayBlockingQueue<>(10);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class FileOpenRequestHandler {
|
||||
|
||||
private void openFiles(OpenFilesEvent evt) {
|
||||
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList();
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
AppLaunchEvent launchEvent = new OpenFileEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class FileOpenRequestHandler {
|
||||
}
|
||||
}).filter(Objects::nonNull).toList();
|
||||
if (!pathsToOpen.isEmpty()) {
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
AppLaunchEvent launchEvent = new OpenFileEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class FileOpenRequestHandler {
|
||||
|
||||
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
|
||||
if (!launchEventQueue.offer(launchEvent)) {
|
||||
LOG.warn("Could not enqueue application launch event.", launchEvent);
|
||||
LOG.warn("Could not enqueue application launch event {}.", launchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
@@ -16,24 +15,28 @@ class IpcMessageHandler implements IpcMessageListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcMessageHandler.class);
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
private final LaunchArgsParser launchArgsParser;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
@Inject
|
||||
public IpcMessageHandler(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
public IpcMessageHandler(LaunchArgsParser launchArgsParser, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchArgsParser = launchArgsParser;
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void revealRunningApp() {
|
||||
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Collections.emptyList()));
|
||||
launchEventQueue.add(new RevealRunningEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(List<String> args) {
|
||||
LOG.debug("Received launch args: {}", args.stream().reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
LOG.debug("Received launch args: {}", args);
|
||||
try {
|
||||
launchArgsParser.process(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Ignoring malformed launch args: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Preprocesses the launch arguments and delegates them to the matching handler.
|
||||
* <p>
|
||||
* An argument is treated as a URI if it starts with a (non-{@code file}) scheme of at least two characters, e.g.
|
||||
* {@code cryptomator://…}. Everything else - including plain paths and {@code file://…} URIs - is treated as a file path
|
||||
* and forwarded to the {@link FileOpenRequestHandler}. The two-character minimum prevents Windows drive letters
|
||||
* (e.g. {@code C:\…}) from being misinterpreted as URIs.
|
||||
* <p>
|
||||
* URIs and file paths must not be mixed and at most a single URI is accepted, which has to be the first argument.
|
||||
*/
|
||||
@Singleton
|
||||
class LaunchArgsParser {
|
||||
|
||||
private static final Pattern SCHEME_PATTERN = Pattern.compile("^([a-zA-Z][a-zA-Z0-9+.-]+):.*");
|
||||
private static final String FILE_SCHEME = "file";
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
private final URIOpenRequestHandler uriOpenRequestHandler;
|
||||
private final NoopRequestHandler noopRequestHandler;
|
||||
|
||||
@Inject
|
||||
public LaunchArgsParser(FileOpenRequestHandler fileOpenRequestHandler, URIOpenRequestHandler uriOpenRequestHandler, NoopRequestHandler noopRequestHandler) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
this.uriOpenRequestHandler = uriOpenRequestHandler;
|
||||
this.noopRequestHandler = noopRequestHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies the given launch arguments and delegates them to the responsible handler.
|
||||
*
|
||||
* @param args the raw launch arguments
|
||||
* @throws IllegalArgumentException if URIs and file paths are mixed, if more than one URI is given, if a URI is not
|
||||
* the first argument, or if a URI argument is malformed
|
||||
*/
|
||||
public void process(List<String> args) {
|
||||
if(args.isEmpty()) {
|
||||
noopRequestHandler.revealApp();
|
||||
return;
|
||||
}
|
||||
|
||||
var classified = args.stream().map(LaunchArgsParser::classify).toList();
|
||||
var uris = classified.stream().filter(arg -> arg.kind() == Kind.URI).toList();
|
||||
|
||||
if (uris.isEmpty()) {
|
||||
var paths = classified.stream().map(Arg::value).toList();
|
||||
fileOpenRequestHandler.handleLaunchArgs(paths);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uris.size() > 1) {
|
||||
throw new IllegalArgumentException("Only a single URI argument is accepted, but got " + uris.size() + ".");
|
||||
}
|
||||
if (classified.getFirst().kind() != Kind.URI) {
|
||||
throw new IllegalArgumentException("URI argument must be the first parameter.");
|
||||
}
|
||||
if (classified.size() > 1) {
|
||||
throw new IllegalArgumentException("Mixing a URI with file paths is not supported.");
|
||||
}
|
||||
uriOpenRequestHandler.handleLaunchArgs(URI.create(classified.getFirst().value()));
|
||||
}
|
||||
|
||||
private static Arg classify(String arg) {
|
||||
var matcher = SCHEME_PATTERN.matcher(arg);
|
||||
if (!matcher.matches()) {
|
||||
return new Arg(Kind.PATH, arg);
|
||||
}
|
||||
var scheme = matcher.group(1);
|
||||
if (FILE_SCHEME.equalsIgnoreCase(scheme)) {
|
||||
// file:// URIs (e.g. passed by Linux file managers) are file paths in disguise
|
||||
return new Arg(Kind.PATH, Path.of(URI.create(arg)).toString());
|
||||
}
|
||||
return new Arg(Kind.URI, arg);
|
||||
}
|
||||
|
||||
private enum Kind {PATH, URI}
|
||||
|
||||
private record Arg(Kind kind, String value) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
@Singleton
|
||||
public class NoopRequestHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NoopRequestHandler.class);
|
||||
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
@Inject
|
||||
public NoopRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
}
|
||||
|
||||
public void revealApp() {
|
||||
AppLaunchEvent launchEvent = new RevealRunningEvent();
|
||||
if (!launchEventQueue.offer(launchEvent)) {
|
||||
LOG.warn("Could not enqueue application launch event {}.", launchEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Requests that the given paths (e.g. {@code .cryptomator} vault files) are opened.
|
||||
*
|
||||
* @param pathsToOpen the paths to open
|
||||
*/
|
||||
public record OpenFileEvent(Collection<Path> pathsToOpen) implements AppLaunchEvent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.ui.keyloading.hub.HubConfig;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Requests opening a Hub vault from an {@code org.cryptomator://vault/open#vaultConfig=…} deeplink.
|
||||
* <p>
|
||||
* The single parameter is the vault's {@code vault.cryptomator}, a compact JWS embedded verbatim, carried in the fragment part.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li> The config is read <em>unverified</em>, since its signature is keyed on the masterkey, which is only obtainable from Hu later on. </li>
|
||||
* <li> The deeplink parsing makes a strict validation due to untrusted input</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param vaultConfig the decoded, unverified vault config
|
||||
* @param vaultId the vault's id within its Hub instance, taken from the config's {@code jti} claim
|
||||
*/
|
||||
public record OpenHubVaultEvent(VaultConfig.UnverifiedVaultConfig vaultConfig, UUID vaultId) implements AppLaunchEvent {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OpenHubVaultEvent.class);
|
||||
private static final String SCHEME = "org.cryptomator";
|
||||
private static final String HOST = "vault";
|
||||
private static final String PATH = "/open";
|
||||
private static final String PARAM_VAULT_CONFIG = "vaultConfig";
|
||||
private static final String HUB_HEADER = "hub";
|
||||
private static final int MAX_CONFIG_LENGTH = 8192; //real Hub vault config is ~1KB leaving some room for extensions
|
||||
|
||||
/**
|
||||
* Attempts to interpret the given URI as an {@code org.cryptomator://vault/open#vaultConfig=…} deeplink.
|
||||
*
|
||||
* @param uri the deeplink URI
|
||||
* @return the parsed event, or an empty optional if the URI's scheme, host or path do not identify a vault-open
|
||||
* deeplink
|
||||
* @throws IllegalArgumentException if the URI identifies a vault-open deeplink, but the config is missing, too
|
||||
* large, not decodable, or does not describe a Hub vault
|
||||
*/
|
||||
public static Optional<OpenHubVaultEvent> tryParse(URI uri) {
|
||||
if (!SCHEME.equalsIgnoreCase(uri.getScheme()) || !HOST.equalsIgnoreCase(uri.getHost()) || !PATH.equals(uri.getPath())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
var params = parseParams(uri.getRawFragment());
|
||||
|
||||
var token = params.get(PARAM_VAULT_CONFIG);
|
||||
if (token == null || token.isBlank()) {
|
||||
throw new IllegalArgumentException("Missing required fragment parameter '" + PARAM_VAULT_CONFIG + "'.");
|
||||
}
|
||||
var vaultConfig = decode(token);
|
||||
requireHubVault(vaultConfig);
|
||||
var vaultId = extractVaultId(vaultConfig);
|
||||
|
||||
var leftoverParams = params.keySet().stream().filter(k -> !k.equals(PARAM_VAULT_CONFIG)).toList();
|
||||
if (!leftoverParams.isEmpty()) {
|
||||
LOG.debug("Ignoring unknown parameters {}", leftoverParams);
|
||||
}
|
||||
|
||||
return Optional.of(new OpenHubVaultEvent(vaultConfig, vaultId));
|
||||
}
|
||||
|
||||
private static VaultConfig.UnverifiedVaultConfig decode(String token) {
|
||||
// a compact JWS is ASCII, so its character count is its byte count
|
||||
if (token.length() > MAX_CONFIG_LENGTH) {
|
||||
throw new IllegalArgumentException("Fragment parameter '%s' must not exceed %d bytes.".formatted(PARAM_VAULT_CONFIG, MAX_CONFIG_LENGTH));
|
||||
}
|
||||
try {
|
||||
return VaultConfig.decode(token);
|
||||
} catch (VaultConfigLoadException e) {
|
||||
throw new IllegalArgumentException("Fragment parameter '" + PARAM_VAULT_CONFIG + "' is not a decodable vault config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the config describes a Hub vault and that the endpoints the app will talk to are usable.
|
||||
*/
|
||||
private static void requireHubVault(VaultConfig.UnverifiedVaultConfig vaultConfig) {
|
||||
var keyIdScheme = vaultConfig.getKeyId().getScheme();
|
||||
if (keyIdScheme == null || !keyIdScheme.startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX)) {
|
||||
throw new IllegalArgumentException("Vault config does not describe a Hub vault, but had key id scheme '" + keyIdScheme + "'.");
|
||||
}
|
||||
HubConfig hubConfig;
|
||||
try {
|
||||
hubConfig = vaultConfig.getHeader(HUB_HEADER, HubConfig.class);
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalArgumentException("Vault config contains an unreadable '" + HUB_HEADER + "' header.", e);
|
||||
}
|
||||
if (hubConfig == null) {
|
||||
throw new IllegalArgumentException("Vault config contains no '" + HUB_HEADER + "' header.");
|
||||
}
|
||||
URI apiBaseUrl;
|
||||
try {
|
||||
apiBaseUrl = hubConfig.getApiBaseUrl();
|
||||
} catch (RuntimeException e) {
|
||||
throw new IllegalArgumentException("Vault config declares no usable hub api base url.", e);
|
||||
}
|
||||
requireUsableEndpoint("apiBaseUrl", apiBaseUrl);
|
||||
requireUsableEndpoint("authEndpoint", toUri("authEndpoint", hubConfig.authEndpoint));
|
||||
}
|
||||
|
||||
private static URI toUri(String field, String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("Vault config declares no hub " + field + ".");
|
||||
}
|
||||
try {
|
||||
return URI.create(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Vault config's hub " + field + " is not a valid url, but was '" + value + "'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireUsableEndpoint(String field, URI uri) {
|
||||
if (!uri.isAbsolute() || uri.getHost() == null) {
|
||||
throw new IllegalArgumentException("Vault config's hub " + field + " is not an absolute url with a host, but was '" + uri + "'.");
|
||||
}
|
||||
// Whether an http host is acceptable (it is, for local development) is decided by CheckHostTrustController
|
||||
// Here we only ensure the endpoint is shaped like something that decision can be made on.
|
||||
var scheme = uri.getScheme();
|
||||
if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) {
|
||||
throw new IllegalArgumentException("Vault config's hub " + field + " is neither http nor https, but was '" + uri + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the vault id from the config's {@code jti} claim.
|
||||
* <p>
|
||||
* Requiring a UUID matters beyond well-formedness: the id is interpolated into the {@code api/vaults/{vaultId}/…}
|
||||
* request path, so it must not be able to introduce a path segment. A {@code jti} is an arbitrary string, so parsing
|
||||
* it as a {@link UUID} and passing that on - rather than the raw claim - is what keeps that guarantee.
|
||||
* <p>
|
||||
* Hub writes the same id into the key id's trailing path segment, and the two have always agreed, so a config where
|
||||
* they differ is forged or broken and is rejected.
|
||||
*/
|
||||
private static UUID extractVaultId(VaultConfig.UnverifiedVaultConfig vaultConfig) {
|
||||
var allegedVaultId = vaultConfig.allegedVaultId();
|
||||
if (allegedVaultId == null || allegedVaultId.isBlank()) {
|
||||
throw new IllegalArgumentException("Vault config declares no vault id.");
|
||||
}
|
||||
UUID vaultId;
|
||||
try {
|
||||
vaultId = UUID.fromString(allegedVaultId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Vault config's vault id is not a uuid, but was '" + allegedVaultId + "'.", e);
|
||||
}
|
||||
var keyId = vaultConfig.getKeyId();
|
||||
var path = keyId.getPath();
|
||||
var lastSegment = path == null ? "" : path.substring(path.lastIndexOf('/') + 1);
|
||||
if (!vaultId.toString().equalsIgnoreCase(lastSegment)) {
|
||||
throw new IllegalArgumentException("Vault config's vault id '" + vaultId + "' does not match its key id '" + keyId + "'.");
|
||||
}
|
||||
return vaultId;
|
||||
}
|
||||
|
||||
private static Map<String, String> parseParams(String rawParams) {
|
||||
var params = new HashMap<String, String>();
|
||||
if (rawParams == null || rawParams.isEmpty()) {
|
||||
return params;
|
||||
}
|
||||
for (var pair : rawParams.split("&")) {
|
||||
var idx = pair.indexOf('=');
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
var key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
|
||||
var value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
|
||||
params.put(key, value);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
/**
|
||||
* Requests that the already-running application instance reveals itself (brings its main window to the front).
|
||||
*/
|
||||
public record RevealRunningEvent() implements AppLaunchEvent {
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class SupportedLanguages {
|
||||
var collator = Collator.getInstance(preferredLocale);
|
||||
collator.setStrength(Collator.PRIMARY);
|
||||
var sorted = new ArrayList<String>();
|
||||
sorted.addFirst(null);
|
||||
sorted.add(0, null);
|
||||
sorted.add(1, ENGLISH);
|
||||
LANGUAGE_TAGS.stream() //
|
||||
.sorted((a, b) -> collator.compare(Locale.forLanguageTag(a).getDisplayName(), Locale.forLanguageTag(b).getDisplayName())) //
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Singleton
|
||||
public class URIOpenRequestHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(URIOpenRequestHandler.class);
|
||||
|
||||
/**
|
||||
* The registered deeplink parsers, tried in order. Each returns a matching event, an empty optional if the URI is
|
||||
* not its concern, or throws {@link IllegalArgumentException} if the URI is its concern but malformed.
|
||||
*/
|
||||
private static final List<Function<URI, Optional<? extends AppLaunchEvent>>> DEEPLINK_PARSERS = List.of( //
|
||||
OpenHubVaultEvent::tryParse //
|
||||
);
|
||||
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
@Inject
|
||||
public URIOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
}
|
||||
|
||||
public void handleLaunchArgs(URI uri) {
|
||||
AppLaunchEvent launchEvent = toLaunchEvent(uri);
|
||||
if (!launchEventQueue.offer(launchEvent)) {
|
||||
LOG.warn("Could not enqueue application launch event {}.", launchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private AppLaunchEvent toLaunchEvent(URI uri) {
|
||||
try {
|
||||
for (var parser : DEEPLINK_PARSERS) {
|
||||
var event = parser.apply(uri);
|
||||
if (event.isPresent()) {
|
||||
return event.get();
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Received malformed deeplink {}: {}. Revealing running app instead.", uri, e.getMessage());
|
||||
return new RevealRunningEvent();
|
||||
}
|
||||
LOG.warn("Received unsupported deeplink {}, revealing running app instead.", uri);
|
||||
return new RevealRunningEvent();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -41,6 +41,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -49,7 +50,7 @@ import java.util.concurrent.ExecutorService;
|
||||
public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class);
|
||||
private static final Path DEFAULT_CUSTOM_VAULT_PATH = Path.of(System.getProperty("user.home"));
|
||||
private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home"));
|
||||
private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator";
|
||||
|
||||
private final Stage window;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class SystemBarUtil {
|
||||
/**
|
||||
* OS Bar placed at the bottom screen edge
|
||||
*/
|
||||
BOTTOM
|
||||
BOTTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,9 +62,9 @@ public class Dialogs {
|
||||
.setOkButtonKey(BUTTON_KEY_CLOSE);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareHubVaultArchived(Stage window, Vault vault) {
|
||||
public SimpleDialog.Builder prepareHubVaultArchived(Stage window, String vaultDisplayName) {
|
||||
return createDialogBuilder().setOwner(window) //
|
||||
.setTitleKey("unlock.title", vault.getDisplayName()) //
|
||||
.setTitleKey("unlock.title", vaultDisplayName) //
|
||||
.setMessageKey("hub.archived.message") //
|
||||
.setDescriptionKey("hub.archived.description") //
|
||||
.setIcon(FontAwesome5Icon.BAN)//
|
||||
|
||||
@@ -4,14 +4,19 @@ import org.cryptomator.common.vaults.NotAVaultDirectoryException;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.launcher.AppLaunchEvent;
|
||||
import org.cryptomator.launcher.OpenFileEvent;
|
||||
import org.cryptomator.launcher.OpenHubVaultEvent;
|
||||
import org.cryptomator.launcher.RevealRunningEvent;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.keyloading.hub.HubVaults;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -31,16 +36,18 @@ class AppLaunchEventHandler {
|
||||
private final ExecutorService executorService;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final VaultService vaultService;
|
||||
private final Stage primaryStage;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
@Inject
|
||||
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) {
|
||||
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, ObservableList<Vault> vaults, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
this.executorService = executorService;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.vaults = vaults;
|
||||
this.vaultService = vaultService;
|
||||
this.primaryStage = primaryStage;
|
||||
this.dialogs = dialogs;
|
||||
@@ -63,10 +70,32 @@ class AppLaunchEventHandler {
|
||||
}
|
||||
|
||||
private void handleLaunchEvent(AppLaunchEvent event) {
|
||||
switch (event.type()) {
|
||||
case REVEAL_APP -> appWindows.showMainWindow();
|
||||
case OPEN_FILE -> event.pathsToOpen().forEach(this::openPotentialVault);
|
||||
default -> LOG.warn("Unsupported event type: {}", event.type());
|
||||
switch (event) {
|
||||
case RevealRunningEvent _ -> appWindows.showMainWindow();
|
||||
case OpenFileEvent openFileEvent -> openFileEvent.pathsToOpen().forEach(this::openPotentialVault);
|
||||
case OpenHubVaultEvent openHubVaultEvent -> openHubVault(openHubVaultEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a hub vault is set up on this machine is a purely local question - hub manages the vault's key, not where
|
||||
* it lives. Only if it is not set up here do we need to ask hub about it.
|
||||
*/
|
||||
private void openHubVault(OpenHubVaultEvent event) {
|
||||
var existing = HubVaults.findByVaultId(vaults, event.vaultId());
|
||||
if (existing.isPresent()) {
|
||||
var vault = existing.get();
|
||||
Platform.runLater(() -> {
|
||||
if (vault.isUnlocked()) {
|
||||
vaultService.reveal(vault);
|
||||
} else if (vault.isLocked()) {
|
||||
appWindows.startUnlockWorkflow(vault, null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//TODO: authenticate, ask hub for the vault's details and offer to add it, see docs/hub-vault-open-deeplink-plan.md
|
||||
LOG.info("Hub vault {} is not set up on this machine.", event.vaultId());
|
||||
appWindows.showMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.cryptomator.integrations.common.Priority;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
@@ -18,17 +17,14 @@ import java.nio.file.Path;
|
||||
*/
|
||||
@DisplayName("JavaFX HostServices (GTK)")
|
||||
@OperatingSystem(OperatingSystem.Value.LINUX)
|
||||
@Priority(Priority.FALLBACK)
|
||||
@Priority(10)
|
||||
public class JfxRevealPathService implements RevealPathService {
|
||||
|
||||
@Override
|
||||
public void reveal(Path p) throws RevealFailedException {
|
||||
var absPath = p.toAbsolutePath();
|
||||
var fxApp = FxApplication.INSTANCE.get();
|
||||
if (fxApp != null) {
|
||||
// showDocument() launches the default app for a regular file, so reveal its parent directory instead
|
||||
var target = Files.isRegularFile(absPath) ? absPath.getParent() : absPath;
|
||||
fxApp.getHostServices().showDocument(target.toUri().toString());
|
||||
fxApp.getHostServices().showDocument(p.toUri().toString());
|
||||
} else {
|
||||
throw new RevealFailedException("JavaFX Application not initialized");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class Check {
|
||||
SUCCEEDED,
|
||||
SKIPPED,
|
||||
ERROR,
|
||||
CANCELLED
|
||||
CANCELLED;
|
||||
}
|
||||
|
||||
ObservableList<Result> getResults() {
|
||||
|
||||
@@ -84,7 +84,7 @@ public class CheckListController implements FxController {
|
||||
|
||||
checks.filtered(c -> !c.isChosenForExecution()).forEach(c -> c.setState(Check.CheckState.SKIPPED));
|
||||
checkExecutor.executeBatch(chosenChecks);
|
||||
checksListView.getSelectionModel().select(chosenChecks.getFirst());
|
||||
checksListView.getSelectionModel().select(chosenChecks.get(0));
|
||||
checksListView.refresh();
|
||||
window.sizeToScene();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingRef;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -64,7 +65,11 @@ abstract class HealthCheckModule {
|
||||
@HealthCheckWindow
|
||||
@HealthCheckScoped
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @HealthCheckWindow Vault vault, @Named("unlockWindow") Stage window ) {
|
||||
return compFactory.create(vault, window).keyloadingStrategy();
|
||||
try {
|
||||
return compFactory.create(KeyLoadingRef.forVault(vault), vault, window).keyloadingStrategy();
|
||||
} catch (IOException e) {
|
||||
return KeyLoadingStrategy.failed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
|
||||
import javafx.stage.Stage;
|
||||
@@ -16,7 +17,14 @@ public interface KeyLoadingComponent {
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
KeyLoadingComponent create(@BindsInstance @KeyLoading Vault vault, @KeyLoading @BindsInstance Stage window);
|
||||
/**
|
||||
* @param vaultRef the {@link KeyLoadingRef} containing the info to load the key
|
||||
* @param vault the local vault, or {@code null} if it is not set up on this machine.
|
||||
* @param window the window to show the key loading scenes in
|
||||
*/
|
||||
KeyLoadingComponent create(@BindsInstance @KeyLoading KeyLoadingRef vaultRef, //
|
||||
@BindsInstance @KeyLoading @Nullable Vault vault, //
|
||||
@BindsInstance @KeyLoading Stage window);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.cryptomator.ui.keyloading;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
@@ -10,7 +9,6 @@ import org.cryptomator.ui.keyloading.hub.HubKeyLoadingModule;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -27,14 +25,10 @@ abstract class KeyLoadingModule {
|
||||
@Provides
|
||||
@KeyLoading
|
||||
@KeyLoadingScoped
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
|
||||
try {
|
||||
String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme();
|
||||
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
|
||||
return strategies.getOrDefault(scheme, () -> fallback).get();
|
||||
} catch (IOException e) {
|
||||
return KeyLoadingStrategy.failed(e);
|
||||
}
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading KeyLoadingRef vaultRef, Map<String, Provider<KeyLoadingStrategy>> strategies) {
|
||||
String scheme = vaultRef.keyId().getScheme();
|
||||
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));
|
||||
return strategies.getOrDefault(scheme, () -> fallback).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.cryptomator.ui.keyloading;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Identifies the vault a key is being loaded for, independently of whether that vault exists on this machine.
|
||||
* <p>
|
||||
* Key loading needs the (unverified) vault config selecting the strategy and addresses
|
||||
* the vault within the strategy, and the display name titles the windows.
|
||||
* <p>
|
||||
* The config is <em>unverified</em>: its signature is keyed on the masterkey, which is exactly what key loading is
|
||||
* about to obtain.
|
||||
*
|
||||
* @param vaultConfig the vault's unverified config
|
||||
* @param displayName the vault's name, as shown to the user
|
||||
*/
|
||||
public record KeyLoadingRef(VaultConfig.UnverifiedVaultConfig vaultConfig, String displayName) {
|
||||
|
||||
/**
|
||||
* Describes a vault that is already set up on this machine.
|
||||
*
|
||||
* @param vault the vault to load a key for
|
||||
* @throws IOException if the vault's config cannot be read
|
||||
*/
|
||||
public static KeyLoadingRef forVault(Vault vault) throws IOException {
|
||||
return new KeyLoadingRef(vault.getVaultConfigCache().get(), vault.getDisplayName());
|
||||
}
|
||||
|
||||
/**
|
||||
* The key id, whose scheme selects the key loading strategy.
|
||||
*/
|
||||
public URI keyId() {
|
||||
return vaultConfig.getKeyId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The vault's id, i.e. how the vault is addressed within its Hub instance.
|
||||
* <p>
|
||||
* Read from the config's {@code jti} claim, which is the authoritative source: the key id carries the same id in its
|
||||
* trailing path segment, but only its <em>scheme</em> is a source of truth here.
|
||||
*/
|
||||
public String vaultId() {
|
||||
return vaultConfig.allegedVaultId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
@@ -15,24 +13,14 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import java.net.URI;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
@@ -47,11 +35,7 @@ public class CheckHostTrustController implements FxController {
|
||||
private static final String CHECK_KEY = "hub.checkHostTrust.message.check";
|
||||
private static final String ASK_SINGULAR_KEY = "hub.checkHostTrust.message.ask";
|
||||
private static final String ASK_PLURAL_KEY = "hub.checkHostTrust.message.ask.plural";
|
||||
private static final String DESCRIPTION_SINGULAR_KEY = "hub.checkHostTrust.description.ask";
|
||||
private static final String DESCRIPTION_PLURAL_KEY = "hub.checkHostTrust.description.ask.plural";
|
||||
private static final String COPY_TOOLTIP_KEY = "hub.checkHostTrust.copyBtn.tooltip";
|
||||
private static final String TRUSTED_CRYPTOMATOR_CLOUD_DOMAIN = ".cryptomator.cloud";
|
||||
private static final Duration COPIED_INDICATION_DURATION = Duration.seconds(2);
|
||||
|
||||
private final Stage window;
|
||||
private final HubConfig hubConfig;
|
||||
@@ -65,10 +49,9 @@ public class CheckHostTrustController implements FxController {
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final SortedSet<String> hostnames;
|
||||
private final StringProperty messageLabel;
|
||||
private final StringProperty descriptionLabel;
|
||||
|
||||
@FXML
|
||||
private VBox hostnamesBox;
|
||||
private TextFlow hostnamesFlow;
|
||||
|
||||
@Inject
|
||||
public CheckHostTrustController(@KeyLoading Stage window, //
|
||||
@@ -91,7 +74,6 @@ public class CheckHostTrustController implements FxController {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.hostnames = new TreeSet<>();
|
||||
this.messageLabel = new SimpleStringProperty(resourceBundle.getString(CHECK_KEY));
|
||||
this.descriptionLabel = new SimpleStringProperty("");
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -135,41 +117,12 @@ public class CheckHostTrustController implements FxController {
|
||||
}
|
||||
|
||||
private void renderHostnames() {
|
||||
hostnamesBox.getChildren().clear();
|
||||
hostnamesFlow.getChildren().clear();
|
||||
for (var hostname : hostnames) {
|
||||
hostnamesBox.getChildren().add(createHostnameRow(hostname));
|
||||
hostnamesFlow.getChildren().add(new Text(hostname + System.lineSeparator()));
|
||||
}
|
||||
var plural = hostnames.size() > 1;
|
||||
messageLabel.set(resourceBundle.getString(plural ? ASK_PLURAL_KEY : ASK_SINGULAR_KEY));
|
||||
descriptionLabel.set(resourceBundle.getString(plural ? DESCRIPTION_PLURAL_KEY : DESCRIPTION_SINGULAR_KEY));
|
||||
}
|
||||
|
||||
private Node createHostnameRow(String hostname) {
|
||||
var label = new Label(hostname);
|
||||
label.setWrapText(true);
|
||||
HBox.setHgrow(label, Priority.ALWAYS);
|
||||
|
||||
var icon = new FontAwesome5IconView();
|
||||
icon.setGlyph(FontAwesome5Icon.COPY);
|
||||
var copyLink = new Hyperlink(null, icon);
|
||||
copyLink.setTooltip(new Tooltip(resourceBundle.getString(COPY_TOOLTIP_KEY)));
|
||||
copyLink.setAccessibleText(resourceBundle.getString(COPY_TOOLTIP_KEY));
|
||||
copyLink.setOnAction(_ -> copyToClipboard(hostname, icon));
|
||||
|
||||
var row = new HBox(6, label, copyLink);
|
||||
row.setAlignment(Pos.CENTER_LEFT);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void copyToClipboard(String hostname, FontAwesome5IconView icon) {
|
||||
var clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putString(hostname);
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
|
||||
icon.setGlyph(FontAwesome5Icon.CHECK);
|
||||
var resetIcon = new PauseTransition(COPIED_INDICATION_DURATION);
|
||||
resetIcon.setOnFinished(_ -> icon.setGlyph(FontAwesome5Icon.COPY));
|
||||
resetIcon.play();
|
||||
var messageKey = hostnames.size() > 1 ? ASK_PLURAL_KEY : ASK_SINGULAR_KEY;
|
||||
messageLabel.set(resourceBundle.getString(messageKey));
|
||||
}
|
||||
|
||||
private boolean isConsistentHubConfig() {
|
||||
@@ -223,12 +176,4 @@ public class CheckHostTrustController implements FxController {
|
||||
return messageLabel.get();
|
||||
}
|
||||
|
||||
public StringProperty descriptionLabelProperty() {
|
||||
return descriptionLabel;
|
||||
}
|
||||
|
||||
public String getDescriptionLabel() {
|
||||
return descriptionLabel.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import org.cryptomator.common.settings.DeviceKey;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -15,13 +14,12 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingRef;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -32,19 +30,15 @@ public abstract class HubKeyLoadingModule {
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static HubConfig provideHubConfig(@KeyLoading Vault vault) {
|
||||
try {
|
||||
return vault.getVaultConfigCache().get().getHeader("hub", HubConfig.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
static HubConfig provideHubConfig(@KeyLoading KeyLoadingRef vaultRef) {
|
||||
return vaultRef.vaultConfig().getHeader("hub", HubConfig.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
@Named("windowTitle")
|
||||
static String provideWindowTitle(@KeyLoading Vault vault, ResourceBundle resourceBundle) {
|
||||
return String.format(resourceBundle.getString("unlock.title"), vault.getDisplayName());
|
||||
static String provideWindowTitle(@KeyLoading KeyLoadingRef vaultRef, ResourceBundle resourceBundle) {
|
||||
return String.format(resourceBundle.getString("unlock.title"), vaultRef.displayName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Locates Hub vaults among the vaults set up on this machine.
|
||||
* <p>
|
||||
* Hub manages a vault's key, not where it lives or how it is laid out, so the local vault list is the only place that
|
||||
* can answer whether a given Hub vault is already set up here.
|
||||
*/
|
||||
public final class HubVaults {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HubVaults.class);
|
||||
|
||||
private HubVaults() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Hub vault with the given id.
|
||||
* <p>
|
||||
* A vault whose config cannot be read - e.g. because it sits on storage that is currently unavailable - is skipped
|
||||
* rather than failing the lookup: one unreachable vault must not prevent finding a different one.
|
||||
*
|
||||
* @param vaults the vaults set up on this machine
|
||||
* @param hubVaultId the vault's id within its Hub instance
|
||||
* @return the local vault, or empty if none of them is that Hub vault
|
||||
*/
|
||||
public static Optional<Vault> findByVaultId(Collection<Vault> vaults, UUID hubVaultId) {
|
||||
return vaults.stream() //
|
||||
.filter(vault -> hasVaultId(vault, hubVaultId)) //
|
||||
.findAny();
|
||||
}
|
||||
|
||||
private static boolean hasVaultId(Vault vault, UUID hubVaultId) {
|
||||
try {
|
||||
return hasVaultId(vault.getVaultConfigCache().get(), hubVaultId);
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Skipping vault {} while looking for hub vault {}, its config is not readable.", vault.getPath(), hubVaultId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasVaultId(VaultConfig.UnverifiedVaultConfig config, UUID hubVaultId) {
|
||||
var keyIdScheme = config.getKeyId().getScheme();
|
||||
if (keyIdScheme == null || !keyIdScheme.startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX)) {
|
||||
return false; //not a hub vault, so it cannot be the one we are looking for
|
||||
}
|
||||
return hubVaultId.toString().equalsIgnoreCase(config.allegedVaultId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,12 +7,12 @@ import com.google.common.base.Preconditions;
|
||||
import com.nimbusds.jose.JWEObject;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingRef;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -60,10 +60,10 @@ public class ReceiveKeyController implements FxController {
|
||||
private final Lazy<Scene> invalidLicenseScene;
|
||||
private final HttpClient httpClient;
|
||||
private final Dialogs dialogs;
|
||||
private final Vault vault;
|
||||
private final KeyLoadingRef vaultRef;
|
||||
|
||||
@Inject
|
||||
public ReceiveKeyController(@KeyLoading Vault vault, //
|
||||
public ReceiveKeyController(@KeyLoading KeyLoadingRef vaultRef, //
|
||||
ExecutorService executor, //
|
||||
@KeyLoading Stage window, //
|
||||
HubConfig hubConfig, //
|
||||
@@ -79,7 +79,7 @@ public class ReceiveKeyController implements FxController {
|
||||
Dialogs dialogs) {
|
||||
this.window = window;
|
||||
this.hubConfig = hubConfig;
|
||||
this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs)
|
||||
this.vaultId = vaultRef.vaultId();
|
||||
this.deviceId = deviceId;
|
||||
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
||||
this.fsOwnerId = fsOwnerId;
|
||||
@@ -92,7 +92,7 @@ public class ReceiveKeyController implements FxController {
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build();
|
||||
this.dialogs = dialogs;
|
||||
this.vault = vault;
|
||||
this.vaultRef = vaultRef;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -313,7 +313,7 @@ public class ReceiveKeyController implements FxController {
|
||||
|
||||
private void accessGoneVaultArchived() {
|
||||
window.close();
|
||||
dialogs.prepareHubVaultArchived((Stage)window.getOwner(), vault).build().showAndWait();
|
||||
dialogs.prepareHubVaultArchived((Stage)window.getOwner(), vaultRef.displayName()).build().showAndWait();
|
||||
}
|
||||
|
||||
private void accountInitializationRequired() {
|
||||
@@ -343,12 +343,6 @@ public class ReceiveKeyController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractVaultId(URI vaultKeyUri) {
|
||||
assert vaultKeyUri.getScheme().startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX);
|
||||
var path = vaultKeyUri.getPath();
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
private record UserDto(@JsonProperty(value = "name", required = true) String name) {}
|
||||
|
||||
|
||||
+4
-2
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -18,6 +19,7 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -41,12 +43,12 @@ public class ChooseMasterkeyFileController implements FxController {
|
||||
|
||||
@Inject
|
||||
public ChooseMasterkeyFileController(@KeyLoading Stage window, //
|
||||
@KeyLoading Vault vault, //
|
||||
@KeyLoading @Nullable Vault vault, //
|
||||
CompletableFuture<Path> result, //
|
||||
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
|
||||
this.result = result;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.resourceBundle = resourceBundle;
|
||||
|
||||
+10
-2
@@ -5,6 +5,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import dagger.multibindings.StringKey;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
@@ -15,20 +16,27 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class})
|
||||
public interface MasterkeyFileLoadingModule {
|
||||
|
||||
/**
|
||||
* Key loading may run for a yet-to-setup vault (i.e. deeplink with only a config) - Masterkey loading requires in the current implementation
|
||||
* an already setup vault.
|
||||
*/
|
||||
String NO_LOCAL_VAULT = "masterkey file loading requires a local vault";
|
||||
|
||||
@Provides
|
||||
@Named("savedPassword")
|
||||
@KeyLoadingScoped
|
||||
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading Vault vault) {
|
||||
static Optional<char[]> provideStoredPassword(KeychainManager keychain, @KeyLoading @Nullable Vault vault) {
|
||||
if (!keychain.isSupported() || keychain.isLocked()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
try {
|
||||
return Optional.ofNullable(keychain.loadPassphrase(vault.getId()));
|
||||
return Optional.ofNullable(keychain.loadPassphrase(Objects.requireNonNull(vault, NO_LOCAL_VAULT).getId()));
|
||||
} catch (KeychainAccessException e) {
|
||||
LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e);
|
||||
return Optional.empty();
|
||||
|
||||
+4
-2
@@ -2,6 +2,7 @@ package org.cryptomator.ui.keyloading.masterkeyfile;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
@@ -25,6 +26,7 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -48,8 +50,8 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
private boolean wrongPassphrase;
|
||||
|
||||
@Inject
|
||||
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional<char[]> savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) {
|
||||
this.vault = vault;
|
||||
public MasterkeyFileLoadingStrategy(@KeyLoading @Nullable Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional<char[]> savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) {
|
||||
this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.window = window;
|
||||
this.passphraseEntry = passphraseEntry;
|
||||
|
||||
+3
-2
@@ -35,6 +35,7 @@ import javafx.scene.transform.Translate;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@@ -66,9 +67,9 @@ public class PassphraseEntryController implements FxController {
|
||||
public Animation unlockAnimation;
|
||||
|
||||
@Inject
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) {
|
||||
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading @Nullable Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain, ExecutorService backgroundExecutorService) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.vault = Objects.requireNonNull(vault, MasterkeyFileLoadingModule.NO_LOCAL_VAULT);
|
||||
this.result = result;
|
||||
this.savedPassword = savedPassword;
|
||||
this.forgetPassword = forgetPassword;
|
||||
|
||||
@@ -153,7 +153,7 @@ public class VaultListController implements FxController {
|
||||
vaults.addListener((ListChangeListener.Change<? extends Vault> c) -> {
|
||||
while (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
Vault anyAddedVault = c.getAddedSubList().getFirst();
|
||||
Vault anyAddedVault = c.getAddedSubList().get(0);
|
||||
vaultList.getSelectionModel().select(anyAddedVault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
/**
|
||||
* Recovery key is not a valid key.
|
||||
*/
|
||||
INVALID
|
||||
INVALID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -140,9 +140,9 @@ public class VaultStatisticsController implements FxController {
|
||||
long allTimeMaxAccessedFiles = Arrays.stream(maxAccessBuf).max().orElse(0L);
|
||||
|
||||
// remove oldest value:
|
||||
decryptedBytesRead.getData().removeFirst();
|
||||
encryptedBytesWrite.getData().removeFirst();
|
||||
accessedFiles.getData().removeFirst();
|
||||
decryptedBytesRead.getData().remove(0);
|
||||
encryptedBytesWrite.getData().remove(0);
|
||||
accessedFiles.getData().remove(0);
|
||||
|
||||
// add latest value:
|
||||
decryptedBytesRead.getData().add(new Data<>(currentStep, decBytes));
|
||||
|
||||
@@ -110,7 +110,7 @@ public class UnlockInvalidMountPointController implements FxController {
|
||||
|
||||
//TODO Add option to show filesystem, e.g. for ExceptionType.HIDEAWAY_EXISTS
|
||||
SHOW_PREFERENCES,
|
||||
SHOW_VAULT_OPTIONS
|
||||
SHOW_VAULT_OPTIONS;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingRef;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -25,6 +26,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -58,7 +60,11 @@ abstract class UnlockModule {
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static KeyLoadingStrategy provideKeyLoadingStrategy(KeyLoadingComponent.Factory compFactory, @UnlockWindow Vault vault, @UnlockWindow Stage window) {
|
||||
return compFactory.create(vault, window).keyloadingStrategy();
|
||||
try {
|
||||
return compFactory.create(KeyLoadingRef.forVault(vault), vault, window).keyloadingStrategy();
|
||||
} catch (IOException e) {
|
||||
return KeyLoadingStrategy.failed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
Binary file not shown.
@@ -20,10 +20,6 @@
|
||||
src: url('firacode_regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('AtkinsonHyperlegibleMono-Regular.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -145,17 +141,6 @@
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
|
||||
.hostname-list {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 6px 8px 6px 8px;
|
||||
}
|
||||
|
||||
.hostname-list .label {
|
||||
-fx-font-family: 'Atkinson Hyperlegible Mono';
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Glyph Icons *
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
src: url('firacode_regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url('AtkinsonHyperlegibleMono-Regular.ttf');
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Root Styling & Colors *
|
||||
@@ -145,17 +141,6 @@
|
||||
-fx-font-size: 1.1em;
|
||||
}
|
||||
|
||||
.hostname-list {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 6px 8px 6px 8px;
|
||||
}
|
||||
|
||||
.hostname-list .label {
|
||||
-fx-font-family: 'Atkinson Hyperlegible Mono';
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Glyph Icons *
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.keyloading.hub.CheckHostTrustController"
|
||||
minWidth="450"
|
||||
maxWidth="450"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_LEFT"
|
||||
@@ -33,15 +34,13 @@
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
<VBox HBox.hgrow="ALWAYS" spacing="6">
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="label-large" text="${controller.messageLabel}" wrapText="true" textAlignment="LEFT">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
<Label text="${controller.descriptionLabel}" wrapText="true" textAlignment="LEFT" visible="${!controller.descriptionLabel.empty}" managed="${!controller.descriptionLabel.empty}"/>
|
||||
<!-- The box is filled in the controller, see createHostNamesRow() -->
|
||||
<VBox fx:id="hostnamesBox" styleClass="hostname-list" spacing="4" visible="${!controller.descriptionLabel.empty}" managed="${!controller.descriptionLabel.empty}"/>
|
||||
<TextFlow fx:id="hostnamesFlow" styleClass="text-flow" minHeight="60"/>
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||
<buttons>
|
||||
|
||||
@@ -173,9 +173,6 @@ hub.noKeychain.openBtn=Open Preferences
|
||||
hub.checkHostTrust.message.check=Checking Configuration…
|
||||
hub.checkHostTrust.message.ask=Trust this host?
|
||||
hub.checkHostTrust.message.ask.plural=Trust these hosts?
|
||||
hub.checkHostTrust.description.ask=Cryptomator does not recognize this host. To unlock the vault, it needs to be trusted. Only trust it if you recognize the address below.
|
||||
hub.checkHostTrust.description.ask.plural=Cryptomator does not recognize these hosts. To unlock the vault, they need to be trusted. Only trust them if you recognize the addresses below.
|
||||
hub.checkHostTrust.copyBtn.tooltip=Copy address to clipboard
|
||||
hub.checkHostTrust.trustBtn=Trust
|
||||
hub.checkHostTrust.denyBtn=Deny
|
||||
### Waiting
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.junit.jupiter.api.condition.EnabledIf;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
@@ -55,7 +56,7 @@ public class EnvironmentTest {
|
||||
List<Path> result = env.getPaths("test.path.property").toList();
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Path.of("/foo/bar/test")));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SettingsJsonTest {
|
||||
|
||||
Assertions.assertTrue(jsonObj.checkForUpdatesEnabled);
|
||||
Assertions.assertEquals(2, jsonObj.directories.size());
|
||||
Assertions.assertEquals("/vault1", jsonObj.directories.getFirst().path);
|
||||
Assertions.assertEquals("/vault1", jsonObj.directories.get(0).path);
|
||||
Assertions.assertEquals("/vault2", jsonObj.directories.get(1).path);
|
||||
Assertions.assertEquals("--foo --bar", jsonObj.directories.get(1).mountFlags);
|
||||
Assertions.assertEquals(8080, jsonObj.port);
|
||||
@@ -67,7 +67,7 @@ public class SettingsJsonTest {
|
||||
public void testSerialize() throws JsonProcessingException {
|
||||
var jsonObj = new SettingsJson();
|
||||
jsonObj.directories = List.of(new VaultSettingsJson(), new VaultSettingsJson());
|
||||
jsonObj.directories.getFirst().id = "test";
|
||||
jsonObj.directories.get(0).id = "test";
|
||||
jsonObj.theme = UiTheme.DARK;
|
||||
jsonObj.showTrayIcon = false;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.mockito.Mockito;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -39,10 +40,9 @@ public class FileOpenRequestHandlerTest {
|
||||
public void testOpenArgsWithCorrectPaths() {
|
||||
inTest.handleLaunchArgs(List.of("foo", "bar"));
|
||||
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
OpenFileEvent evt = Assertions.assertInstanceOf(OpenFileEvent.class, queue.poll());
|
||||
Collection<Path> paths = evt.pathsToOpen();
|
||||
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Path.of("foo"), Path.of("bar")));
|
||||
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,7 +59,7 @@ public class FileOpenRequestHandlerTest {
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||
public void testOpenArgsWithFullQueue() {
|
||||
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
|
||||
queue.add(new OpenFileEvent(Collections.emptyList()));
|
||||
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||
|
||||
inTest.handleLaunchArgs(List.of("foo"));
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
public class LaunchArgsParserTest {
|
||||
|
||||
private FileOpenRequestHandler fileOpenRequestHandler;
|
||||
private URIOpenRequestHandler uriOpenRequestHandler;
|
||||
private NoopRequestHandler noopRequestHandler;
|
||||
private LaunchArgsParser inTest;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
fileOpenRequestHandler = Mockito.mock(FileOpenRequestHandler.class);
|
||||
uriOpenRequestHandler = Mockito.mock(URIOpenRequestHandler.class);
|
||||
noopRequestHandler = Mockito.mock(NoopRequestHandler.class);
|
||||
inTest = new LaunchArgsParser(fileOpenRequestHandler, uriOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("only file paths are forwarded to the FileOpenRequestHandler")
|
||||
public void testOnlyPaths() {
|
||||
inTest.process(List.of("foo", "bar"));
|
||||
|
||||
Mockito.verify(fileOpenRequestHandler).handleLaunchArgs(List.of("foo", "bar"));
|
||||
Mockito.verifyNoInteractions(uriOpenRequestHandler);
|
||||
Mockito.verifyNoInteractions(noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("empty args are forwarded to the NoopRequestHandler")
|
||||
public void testEmptyArgs() {
|
||||
inTest.process(List.of());
|
||||
|
||||
Mockito.verify(noopRequestHandler).revealApp();
|
||||
Mockito.verifyNoInteractions(uriOpenRequestHandler, fileOpenRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a Windows path is not mistaken for a URI")
|
||||
public void testWindowsPathIsNotAUri() {
|
||||
inTest.process(List.of("C:\\Users\\foo\\vault.cryptomator"));
|
||||
|
||||
Mockito.verify(fileOpenRequestHandler).handleLaunchArgs(List.of("C:\\Users\\foo\\vault.cryptomator"));
|
||||
Mockito.verifyNoInteractions(uriOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a single cryptomator:// URI is forwarded to the URIOpenRequestHandler")
|
||||
public void testSingleUri() {
|
||||
inTest.process(List.of("cryptomator://vault/foo"));
|
||||
|
||||
Mockito.verify(uriOpenRequestHandler).handleLaunchArgs(URI.create("cryptomator://vault/foo"));
|
||||
Mockito.verifyNoInteractions(fileOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a file:// URI is converted to a path and forwarded to the FileOpenRequestHandler")
|
||||
public void testFileUriIsTreatedAsPath() {
|
||||
inTest.process(List.of("file:///tmp/vault.cryptomator"));
|
||||
|
||||
var captor = ArgumentCaptor.forClass(List.class);
|
||||
Mockito.verify(fileOpenRequestHandler).handleLaunchArgs(captor.capture());
|
||||
Mockito.verifyNoInteractions(uriOpenRequestHandler, noopRequestHandler);
|
||||
Assertions.assertEquals(1, captor.getValue().size());
|
||||
Assertions.assertFalse(captor.getValue().getFirst().toString().startsWith("file:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("mixing a URI with a file path fails")
|
||||
public void testMixedUriAndPathFails() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> inTest.process(List.of("cryptomator://vault/foo", "bar")));
|
||||
Mockito.verifyNoInteractions(fileOpenRequestHandler, uriOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("more than one URI fails")
|
||||
public void testMultipleUrisFail() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> inTest.process(List.of("cryptomator://vault/foo", "cryptomator://vault/bar")));
|
||||
Mockito.verifyNoInteractions(fileOpenRequestHandler, uriOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a URI that is not the first parameter fails")
|
||||
public void testUriNotFirstFails() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> inTest.process(List.of("foo", "cryptomator://vault/bar")));
|
||||
Mockito.verifyNoInteractions(fileOpenRequestHandler, uriOpenRequestHandler, noopRequestHandler);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class OpenHubVaultEventTest {
|
||||
|
||||
private static final String VAULT_ID = "d3a1f0b2-7c4e-4a1d-9f3b-2e5c6a7b8c9d";
|
||||
private static final String KEY_ID = "hub+https://hub.example.com/api/vaults/" + VAULT_ID;
|
||||
private static final String TOKEN = hubVaultConfig(KEY_ID);
|
||||
|
||||
@Test
|
||||
@DisplayName("a valid vault/open deeplink is parsed")
|
||||
public void testValid() {
|
||||
var inTest = OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + TOKEN)).orElseThrow();
|
||||
|
||||
Assertions.assertEquals(UUID.fromString(VAULT_ID), inTest.vaultId());
|
||||
Assertions.assertEquals(URI.create(KEY_ID), inTest.vaultConfig().getKeyId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("parameters in the query instead of the fragment are not accepted")
|
||||
public void testQueryParamsRejected() {
|
||||
var uri = URI.create("org.cryptomator://vault/open?vaultConfig=" + TOKEN);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> OpenHubVaultEvent.tryParse(uri));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an encoded separator inside a value cannot forge another parameter")
|
||||
public void testNoParameterInjectionViaEncodedSeparator() {
|
||||
// '%26' must stay part of the first value; decoding the fragment before splitting would turn it into a real
|
||||
// separator and smuggle in a 'vaultConfig' the link never carried - hence getRawFragment(), decoding per value.
|
||||
var uri = URI.create("org.cryptomator://vault/open#other=a%26vaultConfig=" + TOKEN);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> OpenHubVaultEvent.tryParse(uri));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a config exceeding the size limit is rejected")
|
||||
public void testExceedsSizeLimit() {
|
||||
var oversized = "a".repeat(8193);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + oversized)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a config that is not a decodable token is rejected")
|
||||
public void testNotAToken() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=not-a-jwt")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a config without a hub key id is rejected")
|
||||
public void testNotAHubVault() {
|
||||
var token = hubVaultConfig("masterkeyfile:masterkey.cryptomator");
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a config without a hub header is rejected")
|
||||
public void testNoHubHeader() {
|
||||
var token = vaultConfig(KEY_ID, VAULT_ID, null);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("a vault id that is not a uuid is rejected")
|
||||
@ValueSource(strings = { //
|
||||
"not-a-uuid", //
|
||||
"", // absent
|
||||
"../../evil" // must never reach the api/vaults/{vaultId}/... request path
|
||||
})
|
||||
public void testInvalidVaultId(String vaultId) {
|
||||
var token = vaultConfig(KEY_ID, vaultId, hubHeader("https://hub.example.com/api", "https://login.example.com/auth"));
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a vault id disagreeing with the key id is rejected")
|
||||
public void testVaultIdMismatch() {
|
||||
// Hub writes the same id into both, so a config where they differ is forged or broken
|
||||
var token = vaultConfig(KEY_ID, "11111111-2222-3333-4444-555555555555", //
|
||||
hubHeader("https://hub.example.com/api", "https://login.example.com/auth"));
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@DisplayName("an unusable hub endpoint is rejected")
|
||||
@ValueSource(strings = { //
|
||||
"ftp://hub.example.com/api", // neither http nor https
|
||||
"/api", // not absolute
|
||||
"https:///api" // no host
|
||||
})
|
||||
public void testUnusableApiBaseUrl(String apiBaseUrl) {
|
||||
var token = vaultConfig(KEY_ID, VAULT_ID, hubHeader(apiBaseUrl, "https://login.example.com/auth"));
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an unusable auth endpoint is rejected")
|
||||
public void testUnusableAuthEndpoint() {
|
||||
var token = vaultConfig(KEY_ID, VAULT_ID, hubHeader("https://hub.example.com/api", "not a url"));
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, //
|
||||
() -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an http endpoint is accepted here, host trust decides later")
|
||||
public void testHttpEndpointAccepted() {
|
||||
var token = vaultConfig(KEY_ID, VAULT_ID, hubHeader("http://localhost:8080/api", "http://localhost:8080/auth"));
|
||||
|
||||
Assertions.assertTrue(OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=" + token)).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a non-cryptomator scheme yields empty")
|
||||
public void testWrongScheme() {
|
||||
Assertions.assertEquals(Optional.empty(), OpenHubVaultEvent.tryParse(URI.create("foobar://vault/open#vaultConfig=" + TOKEN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("the bare cryptomator scheme is no longer recognized")
|
||||
public void testLegacyScheme() {
|
||||
Assertions.assertEquals(Optional.empty(), OpenHubVaultEvent.tryParse(URI.create("cryptomator://vault/open#vaultConfig=" + TOKEN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an unknown host yields empty")
|
||||
public void testWrongHost() {
|
||||
Assertions.assertEquals(Optional.empty(), OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://foo/open#vaultConfig=" + TOKEN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("an unknown path yields empty")
|
||||
public void testWrongPath() {
|
||||
Assertions.assertEquals(Optional.empty(), OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/create#vaultConfig=" + TOKEN)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a matching host is recognized case-insensitively")
|
||||
public void testHostCaseInsensitive() {
|
||||
Assertions.assertTrue(OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://VAULT/open#vaultConfig=" + TOKEN)).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a missing config fails")
|
||||
public void testMissingConfig() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a blank config fails")
|
||||
public void testBlankConfig() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> OpenHubVaultEvent.tryParse(URI.create("org.cryptomator://vault/open#vaultConfig=")));
|
||||
}
|
||||
|
||||
private static String hubHeader(String apiBaseUrl, String authEndpoint) {
|
||||
return """
|
||||
,
|
||||
"hub": {
|
||||
"clientId":"cryptomator",\
|
||||
"authEndpoint":"%s",\
|
||||
"tokenEndpoint":"https://login.example.com/token",\
|
||||
"authSuccessUrl":"https://hub.example.com/app/unlock-success",\
|
||||
"authErrorUrl":"https://hub.example.com/app/unlock-error",\
|
||||
"apiBaseUrl":"%s"
|
||||
}""".formatted(authEndpoint, apiBaseUrl);
|
||||
}
|
||||
|
||||
private static String hubVaultConfig(String keyId) {
|
||||
return vaultConfig(keyId, VAULT_ID, hubHeader("https://hub.example.com/api", "https://login.example.com/auth"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a vault config token. Its signature is keyed on the masterkey, which the deeplink never carries, so a dummy
|
||||
* signature is exactly what the parser operates on.
|
||||
*/
|
||||
private static String vaultConfig(String keyId, String vaultId, String extraHeaderFields) {
|
||||
var header = """
|
||||
{ "kid":"%s",\
|
||||
"typ":"JWT",\
|
||||
"alg":"HS256"\
|
||||
%s
|
||||
}""".formatted(keyId, extraHeaderFields == null ? "" : extraHeaderFields);
|
||||
var payload = """
|
||||
{ "jti":"%s",\
|
||||
"format":8,\
|
||||
"cipherCombo":"SIV_GCM",\
|
||||
"shorteningThreshold":220\
|
||||
}""".formatted(vaultId);
|
||||
var encoder = Base64.getUrlEncoder().withoutPadding();
|
||||
return encoder.encodeToString(header.getBytes(StandardCharsets.UTF_8)) //
|
||||
+ "." + encoder.encodeToString(payload.getBytes(StandardCharsets.UTF_8)) //
|
||||
+ "." + encoder.encodeToString("signature".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.launcher.AppLaunchEvent;
|
||||
import org.cryptomator.launcher.RevealRunningEvent;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class AppLaunchEventHandlerTest {
|
||||
|
||||
private BlockingQueue<AppLaunchEvent> queue;
|
||||
private ExecutorService executor;
|
||||
private FxApplicationWindows appWindows;
|
||||
private AppLaunchEventHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
queue = new LinkedBlockingQueue<>();
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
appWindows = mock(FxApplicationWindows.class);
|
||||
handler = new AppLaunchEventHandler(queue, executor, appWindows, mock(VaultListManager.class), FXCollections.observableArrayList(), mock(VaultService.class), mock(Stage.class), mock(Dialogs.class));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void teardown() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
// TODO: Add test for OpenHubVaultEvent once the event opens the hub vault flow for real.
|
||||
|
||||
@Test
|
||||
@DisplayName("a RevealRunningEvent reveals the main window")
|
||||
public void testRevealRunningEventShowsMainWindow() {
|
||||
queue.add(new RevealRunningEvent());
|
||||
|
||||
handler.startHandlingLaunchEvents();
|
||||
|
||||
verify(appWindows, timeout(2000)).showMainWindow();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class HubVaultsTest {
|
||||
|
||||
private static final UUID VAULT_ID = UUID.fromString("d3a1f0b2-7c4e-4a1d-9f3b-2e5c6a7b8c9d");
|
||||
private static final UUID OTHER_VAULT_ID = UUID.fromString("11111111-2222-3333-4444-555555555555");
|
||||
|
||||
@Test
|
||||
@DisplayName("the hub vault with the requested id is found")
|
||||
public void testFindsMatchingVault() throws IOException {
|
||||
var wanted = hubVault(VAULT_ID);
|
||||
var vaults = List.of(hubVault(OTHER_VAULT_ID), wanted);
|
||||
|
||||
Assertions.assertEquals(Optional.of(wanted), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("no vault with the requested id yields empty")
|
||||
public void testNoMatch() throws IOException {
|
||||
var vaults = List.of(hubVault(OTHER_VAULT_ID));
|
||||
|
||||
Assertions.assertEquals(Optional.empty(), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a password vault never matches, even carrying the same id")
|
||||
public void testIgnoresPasswordVault() throws IOException {
|
||||
var vaults = List.of(vault("masterkeyfile:masterkey.cryptomator", VAULT_ID));
|
||||
|
||||
Assertions.assertEquals(Optional.empty(), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a vault whose config cannot be read is skipped, not fatal")
|
||||
public void testSkipsUnreadableVault() throws IOException {
|
||||
// e.g. a vault on a network drive that is currently offline - it must not hide a vault further down the list
|
||||
var wanted = hubVault(VAULT_ID);
|
||||
var vaults = List.of(unreadableVault(), wanted);
|
||||
|
||||
Assertions.assertEquals(Optional.of(wanted), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
|
||||
// setup/mock provider
|
||||
|
||||
private static Vault hubVault(UUID vaultId) throws IOException {
|
||||
return vault("hub+https://hub.example.com/api/vaults/" + vaultId, vaultId);
|
||||
}
|
||||
|
||||
private static Vault vault(String keyId, UUID vaultId) throws IOException {
|
||||
var configCache = mock(VaultConfigCache.class);
|
||||
var config = mockConfig(keyId, vaultId);
|
||||
when(configCache.get()).thenReturn(config);
|
||||
return vaultWith(configCache);
|
||||
}
|
||||
|
||||
private static Vault unreadableVault() throws IOException {
|
||||
var configCache = mock(VaultConfigCache.class);
|
||||
when(configCache.get()).thenThrow(new IOException("vault directory unavailable"));
|
||||
return vaultWith(configCache);
|
||||
}
|
||||
|
||||
private static Vault vaultWith(VaultConfigCache configCache) {
|
||||
var vault = mock(Vault.class);
|
||||
when(vault.getVaultConfigCache()).thenReturn(configCache);
|
||||
return vault;
|
||||
}
|
||||
|
||||
private static UnverifiedVaultConfig mockConfig(String keyId, UUID vaultId) {
|
||||
var mock = mock(UnverifiedVaultConfig.class);
|
||||
when(mock.getKeyId()).thenReturn(URI.create(keyId));
|
||||
when(mock.allegedVaultId()).thenReturn(vaultId.toString());
|
||||
return mock;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user